2022-01-28 22:39:44 +01:00
|
|
|
//===-- CallInterface.cpp -- Procedure call interface ---------------------===//
|
|
|
|
|
//
|
|
|
|
|
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
|
|
|
|
|
// See https://llvm.org/LICENSE.txt for license information.
|
|
|
|
|
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
|
|
|
|
//
|
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
|
|
|
|
|
|
#include "flang/Lower/CallInterface.h"
|
|
|
|
|
#include "flang/Evaluate/fold.h"
|
|
|
|
|
#include "flang/Lower/Bridge.h"
|
|
|
|
|
#include "flang/Lower/Mangler.h"
|
|
|
|
|
#include "flang/Lower/PFTBuilder.h"
|
2022-03-07 19:55:48 +01:00
|
|
|
#include "flang/Lower/StatementContext.h"
|
2022-01-28 22:39:44 +01:00
|
|
|
#include "flang/Lower/Support/Utils.h"
|
2022-03-07 19:55:48 +01:00
|
|
|
#include "flang/Optimizer/Builder/Character.h"
|
2022-01-28 22:39:44 +01:00
|
|
|
#include "flang/Optimizer/Builder/FIRBuilder.h"
|
2022-06-10 08:50:40 +02:00
|
|
|
#include "flang/Optimizer/Builder/Todo.h"
|
2022-01-28 22:39:44 +01:00
|
|
|
#include "flang/Optimizer/Dialect/FIRDialect.h"
|
|
|
|
|
#include "flang/Optimizer/Dialect/FIROpsSupport.h"
|
|
|
|
|
#include "flang/Optimizer/Support/InternalNames.h"
|
|
|
|
|
#include "flang/Semantics/symbol.h"
|
|
|
|
|
#include "flang/Semantics/tools.h"
|
2023-01-07 20:55:47 -08:00
|
|
|
#include <optional>
|
2022-01-28 22:39:44 +01:00
|
|
|
|
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
|
// BIND(C) mangling helpers
|
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
|
|
|
|
|
|
// Return the binding label (from BIND(C...)) or the mangled name of a symbol.
|
[flang] Block construct
A block construct is an execution control construct that supports
declaration scopes contained within a parent subprogram scope or another
block scope. (blocks may be nested.) This is implemented by applying
basic scope processing to the block level.
Name uniquing/mangling is extended to support this. The term "block" is
heavily overloaded in Fortran standards. Prior name uniquing used tag `B`
for common block objects. Existing tag choices were modified to free up `B`
for block construct entities, and `C` for common blocks, and resolve
additional issues with other tags. The "old tag -> new tag" changes can
be summarized as:
-> B -- block construct -> new
B -> C -- common block
C -> YI -- intrinsic type descriptor; not currently generated
CT -> Y -- nonintrinsic type descriptor; not currently generated
G -> N -- namelist group
L -> -- block data; not needed -> deleted
Existing name uniquing components consist of a tag followed by a name
from user source code, such as a module, subprogram, or variable name.
Block constructs are different in that they may be anonymous. (Like other
constructs, a block may have a `block-construct-name` that can be used
in exit statements, but this name is optional.) So blocks are given a
numeric compiler-generated preorder index starting with `B1`, `B2`,
and so on, on a per-procedure basis.
Name uniquing is also modified to include component names for all
containing procedures rather than for just the immediate host. This
fixes an existing name clash bug with same-named entities in same-named
host subprograms contained in different-named containing subprograms,
and variations of the bug involving modules and submodules.
F18 clause 9.7.3.1 (Deallocation of allocatable variables) paragraph 1
has a requirement that an allocated, unsaved allocatable local variable
must be deallocated on procedure exit. The following paragraph 2 states:
When a BLOCK construct terminates, any unsaved allocated allocatable
local variable of the construct is deallocated.
Similarly, F18 clause 7.5.6.3 (When finalization occurs) paragraph 3
has a requirement that a nonpointer, nonallocatable object must be
finalized on procedure exit. The following paragraph 4 states:
A nonpointer nonallocatable local variable of a BLOCK construct
is finalized immediately before it would become undefined due to
termination of the BLOCK construct.
These deallocation and finalization requirements, along with stack
restoration requirements, require knowledge of block exits. In addition
to normal block termination at an end-block-stmt, a block may be
terminated by executing a branching statement that targets a statement
outside of the block. This includes
Single-target branch statements:
- goto
- exit
- cycle
- return
Bounded multiple-target branch statements:
- arithmetic goto
- IO statement with END, EOR, or ERR specifiers
Unbounded multiple-target branch statements:
- call with alternate return specs
- computed goto
- assigned goto
Lowering code is extended to determine if one of these branches exits
one or more relevant blocks or other constructs, and adds a mechanism to
insert any necessary deallocation, finalization, or stack restoration
code at the source of the branch. For a single-target branch it suffices
to generate the exit code just prior to taking the indicated branch.
Each target of a multiple-target branch must be analyzed individually.
Where necessary, the code must first branch to an intermediate basic
block that contains exit code, followed by a branch to the original target
statement.
This patch implements an `activeConstructStack` construct exit mechanism
that queries a new `activeConstruct` PFT bit to insert stack restoration
code at block exits. It ties in to existing code in ConvertVariable.cpp
routine `instantiateLocal` which has code for finalization, making block
exit finalization on par with subprogram exit finalization. Deallocation
is as yet unimplemented for subprograms or blocks. This may result in
memory leaks for affected objects at either the subprogram or block level.
Deallocation cases can be addressed uniformly for both scopes in a future
patch, presumably with code insertion in routine `instantiateLocal`.
The exit code mechanism is not limited to block construct exits. It is
also available for use with other constructs. In particular, it is used
to replace custom deallocation code for a select case construct character
selector expression where applicable. This functionality is also added
to select type and associate constructs. It is available for use with
other constructs, such as select rank and image control constructs,
if that turns out to be necessary.
Overlapping nonfunctional changes include eliminating "FIR" from some
routine names and eliminating obsolete spaces in comments.
2023-02-27 14:05:53 -08:00
|
|
|
static std::string getMangledName(Fortran::lower::AbstractConverter &converter,
|
2022-06-22 20:46:30 +02:00
|
|
|
const Fortran::semantics::Symbol &symbol) {
|
2022-01-28 22:39:44 +01:00
|
|
|
const std::string *bindName = symbol.GetBindName();
|
2022-06-22 20:46:30 +02:00
|
|
|
// TODO: update GetBindName so that it does not return a label for internal
|
|
|
|
|
// procedures.
|
|
|
|
|
if (bindName && Fortran::semantics::ClassifyProcedure(symbol) ==
|
|
|
|
|
Fortran::semantics::ProcedureDefinitionClass::Internal)
|
[flang] Block construct
A block construct is an execution control construct that supports
declaration scopes contained within a parent subprogram scope or another
block scope. (blocks may be nested.) This is implemented by applying
basic scope processing to the block level.
Name uniquing/mangling is extended to support this. The term "block" is
heavily overloaded in Fortran standards. Prior name uniquing used tag `B`
for common block objects. Existing tag choices were modified to free up `B`
for block construct entities, and `C` for common blocks, and resolve
additional issues with other tags. The "old tag -> new tag" changes can
be summarized as:
-> B -- block construct -> new
B -> C -- common block
C -> YI -- intrinsic type descriptor; not currently generated
CT -> Y -- nonintrinsic type descriptor; not currently generated
G -> N -- namelist group
L -> -- block data; not needed -> deleted
Existing name uniquing components consist of a tag followed by a name
from user source code, such as a module, subprogram, or variable name.
Block constructs are different in that they may be anonymous. (Like other
constructs, a block may have a `block-construct-name` that can be used
in exit statements, but this name is optional.) So blocks are given a
numeric compiler-generated preorder index starting with `B1`, `B2`,
and so on, on a per-procedure basis.
Name uniquing is also modified to include component names for all
containing procedures rather than for just the immediate host. This
fixes an existing name clash bug with same-named entities in same-named
host subprograms contained in different-named containing subprograms,
and variations of the bug involving modules and submodules.
F18 clause 9.7.3.1 (Deallocation of allocatable variables) paragraph 1
has a requirement that an allocated, unsaved allocatable local variable
must be deallocated on procedure exit. The following paragraph 2 states:
When a BLOCK construct terminates, any unsaved allocated allocatable
local variable of the construct is deallocated.
Similarly, F18 clause 7.5.6.3 (When finalization occurs) paragraph 3
has a requirement that a nonpointer, nonallocatable object must be
finalized on procedure exit. The following paragraph 4 states:
A nonpointer nonallocatable local variable of a BLOCK construct
is finalized immediately before it would become undefined due to
termination of the BLOCK construct.
These deallocation and finalization requirements, along with stack
restoration requirements, require knowledge of block exits. In addition
to normal block termination at an end-block-stmt, a block may be
terminated by executing a branching statement that targets a statement
outside of the block. This includes
Single-target branch statements:
- goto
- exit
- cycle
- return
Bounded multiple-target branch statements:
- arithmetic goto
- IO statement with END, EOR, or ERR specifiers
Unbounded multiple-target branch statements:
- call with alternate return specs
- computed goto
- assigned goto
Lowering code is extended to determine if one of these branches exits
one or more relevant blocks or other constructs, and adds a mechanism to
insert any necessary deallocation, finalization, or stack restoration
code at the source of the branch. For a single-target branch it suffices
to generate the exit code just prior to taking the indicated branch.
Each target of a multiple-target branch must be analyzed individually.
Where necessary, the code must first branch to an intermediate basic
block that contains exit code, followed by a branch to the original target
statement.
This patch implements an `activeConstructStack` construct exit mechanism
that queries a new `activeConstruct` PFT bit to insert stack restoration
code at block exits. It ties in to existing code in ConvertVariable.cpp
routine `instantiateLocal` which has code for finalization, making block
exit finalization on par with subprogram exit finalization. Deallocation
is as yet unimplemented for subprograms or blocks. This may result in
memory leaks for affected objects at either the subprogram or block level.
Deallocation cases can be addressed uniformly for both scopes in a future
patch, presumably with code insertion in routine `instantiateLocal`.
The exit code mechanism is not limited to block construct exits. It is
also available for use with other constructs. In particular, it is used
to replace custom deallocation code for a select case construct character
selector expression where applicable. This functionality is also added
to select type and associate constructs. It is available for use with
other constructs, such as select rank and image control constructs,
if that turns out to be necessary.
Overlapping nonfunctional changes include eliminating "FIR" from some
routine names and eliminating obsolete spaces in comments.
2023-02-27 14:05:53 -08:00
|
|
|
TODO(converter.getCurrentLocation(), "BIND(C) internal procedures");
|
|
|
|
|
return bindName ? *bindName : converter.mangleName(symbol);
|
2022-01-28 22:39:44 +01:00
|
|
|
}
|
|
|
|
|
|
2023-02-09 09:02:43 +01:00
|
|
|
mlir::Type Fortran::lower::getUntypedBoxProcType(mlir::MLIRContext *context) {
|
|
|
|
|
llvm::SmallVector<mlir::Type> resultTys;
|
|
|
|
|
llvm::SmallVector<mlir::Type> inputTys;
|
|
|
|
|
auto untypedFunc = mlir::FunctionType::get(context, inputTys, resultTys);
|
|
|
|
|
return fir::BoxProcType::get(context, untypedFunc);
|
|
|
|
|
}
|
|
|
|
|
|
2022-03-07 19:55:48 +01:00
|
|
|
/// Return the type of a dummy procedure given its characteristic (if it has
|
|
|
|
|
/// one).
|
2023-02-09 09:02:43 +01:00
|
|
|
static mlir::Type getProcedureDesignatorType(
|
2022-03-07 19:55:48 +01:00
|
|
|
const Fortran::evaluate::characteristics::Procedure *,
|
|
|
|
|
Fortran::lower::AbstractConverter &converter) {
|
|
|
|
|
// TODO: Get actual function type of the dummy procedure, at least when an
|
|
|
|
|
// interface is given. The result type should be available even if the arity
|
|
|
|
|
// and type of the arguments is not.
|
|
|
|
|
// In general, that is a nice to have but we cannot guarantee to find the
|
|
|
|
|
// function type that will match the one of the calls, we may not even know
|
|
|
|
|
// how many arguments the dummy procedure accepts (e.g. if a procedure
|
|
|
|
|
// pointer is only transiting through the current procedure without being
|
|
|
|
|
// called), so a function type cast must always be inserted.
|
2023-02-09 09:02:43 +01:00
|
|
|
return Fortran::lower::getUntypedBoxProcType(&converter.getMLIRContext());
|
2022-03-07 19:55:48 +01:00
|
|
|
}
|
|
|
|
|
|
2022-02-23 19:48:07 +01:00
|
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
|
// Caller side interface implementation
|
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
|
|
|
|
|
|
bool Fortran::lower::CallerInterface::hasAlternateReturns() const {
|
|
|
|
|
return procRef.hasAlternateReturns();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
std::string Fortran::lower::CallerInterface::getMangledName() const {
|
|
|
|
|
const Fortran::evaluate::ProcedureDesignator &proc = procRef.proc();
|
|
|
|
|
if (const Fortran::semantics::Symbol *symbol = proc.GetSymbol())
|
[flang] Block construct
A block construct is an execution control construct that supports
declaration scopes contained within a parent subprogram scope or another
block scope. (blocks may be nested.) This is implemented by applying
basic scope processing to the block level.
Name uniquing/mangling is extended to support this. The term "block" is
heavily overloaded in Fortran standards. Prior name uniquing used tag `B`
for common block objects. Existing tag choices were modified to free up `B`
for block construct entities, and `C` for common blocks, and resolve
additional issues with other tags. The "old tag -> new tag" changes can
be summarized as:
-> B -- block construct -> new
B -> C -- common block
C -> YI -- intrinsic type descriptor; not currently generated
CT -> Y -- nonintrinsic type descriptor; not currently generated
G -> N -- namelist group
L -> -- block data; not needed -> deleted
Existing name uniquing components consist of a tag followed by a name
from user source code, such as a module, subprogram, or variable name.
Block constructs are different in that they may be anonymous. (Like other
constructs, a block may have a `block-construct-name` that can be used
in exit statements, but this name is optional.) So blocks are given a
numeric compiler-generated preorder index starting with `B1`, `B2`,
and so on, on a per-procedure basis.
Name uniquing is also modified to include component names for all
containing procedures rather than for just the immediate host. This
fixes an existing name clash bug with same-named entities in same-named
host subprograms contained in different-named containing subprograms,
and variations of the bug involving modules and submodules.
F18 clause 9.7.3.1 (Deallocation of allocatable variables) paragraph 1
has a requirement that an allocated, unsaved allocatable local variable
must be deallocated on procedure exit. The following paragraph 2 states:
When a BLOCK construct terminates, any unsaved allocated allocatable
local variable of the construct is deallocated.
Similarly, F18 clause 7.5.6.3 (When finalization occurs) paragraph 3
has a requirement that a nonpointer, nonallocatable object must be
finalized on procedure exit. The following paragraph 4 states:
A nonpointer nonallocatable local variable of a BLOCK construct
is finalized immediately before it would become undefined due to
termination of the BLOCK construct.
These deallocation and finalization requirements, along with stack
restoration requirements, require knowledge of block exits. In addition
to normal block termination at an end-block-stmt, a block may be
terminated by executing a branching statement that targets a statement
outside of the block. This includes
Single-target branch statements:
- goto
- exit
- cycle
- return
Bounded multiple-target branch statements:
- arithmetic goto
- IO statement with END, EOR, or ERR specifiers
Unbounded multiple-target branch statements:
- call with alternate return specs
- computed goto
- assigned goto
Lowering code is extended to determine if one of these branches exits
one or more relevant blocks or other constructs, and adds a mechanism to
insert any necessary deallocation, finalization, or stack restoration
code at the source of the branch. For a single-target branch it suffices
to generate the exit code just prior to taking the indicated branch.
Each target of a multiple-target branch must be analyzed individually.
Where necessary, the code must first branch to an intermediate basic
block that contains exit code, followed by a branch to the original target
statement.
This patch implements an `activeConstructStack` construct exit mechanism
that queries a new `activeConstruct` PFT bit to insert stack restoration
code at block exits. It ties in to existing code in ConvertVariable.cpp
routine `instantiateLocal` which has code for finalization, making block
exit finalization on par with subprogram exit finalization. Deallocation
is as yet unimplemented for subprograms or blocks. This may result in
memory leaks for affected objects at either the subprogram or block level.
Deallocation cases can be addressed uniformly for both scopes in a future
patch, presumably with code insertion in routine `instantiateLocal`.
The exit code mechanism is not limited to block construct exits. It is
also available for use with other constructs. In particular, it is used
to replace custom deallocation code for a select case construct character
selector expression where applicable. This functionality is also added
to select type and associate constructs. It is available for use with
other constructs, such as select rank and image control constructs,
if that turns out to be necessary.
Overlapping nonfunctional changes include eliminating "FIR" from some
routine names and eliminating obsolete spaces in comments.
2023-02-27 14:05:53 -08:00
|
|
|
return ::getMangledName(converter, symbol->GetUltimate());
|
2022-02-23 19:48:07 +01:00
|
|
|
assert(proc.GetSpecificIntrinsic() &&
|
|
|
|
|
"expected intrinsic procedure in designator");
|
|
|
|
|
return proc.GetName();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const Fortran::semantics::Symbol *
|
|
|
|
|
Fortran::lower::CallerInterface::getProcedureSymbol() const {
|
|
|
|
|
return procRef.proc().GetSymbol();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool Fortran::lower::CallerInterface::isIndirectCall() const {
|
|
|
|
|
if (const Fortran::semantics::Symbol *symbol = procRef.proc().GetSymbol())
|
|
|
|
|
return Fortran::semantics::IsPointer(*symbol) ||
|
|
|
|
|
Fortran::semantics::IsDummy(*symbol);
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2022-10-12 15:24:21 +02:00
|
|
|
bool Fortran::lower::CallerInterface::requireDispatchCall() const {
|
|
|
|
|
// calls with NOPASS attribute still have their component so check if it is
|
|
|
|
|
// polymorphic.
|
|
|
|
|
if (const Fortran::evaluate::Component *component =
|
|
|
|
|
procRef.proc().GetComponent()) {
|
|
|
|
|
if (Fortran::semantics::IsPolymorphic(component->GetFirstSymbol()))
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
// calls with PASS attribute have the passed-object already set in its
|
|
|
|
|
// arguments. Just check if their is one.
|
|
|
|
|
std::optional<unsigned> passArg = getPassArgIndex();
|
|
|
|
|
if (passArg)
|
|
|
|
|
return true;
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
std::optional<unsigned>
|
|
|
|
|
Fortran::lower::CallerInterface::getPassArgIndex() const {
|
|
|
|
|
unsigned passArgIdx = 0;
|
2023-01-14 14:06:18 -08:00
|
|
|
std::optional<unsigned> passArg;
|
2022-10-12 15:24:21 +02:00
|
|
|
for (const auto &arg : getCallDescription().arguments()) {
|
|
|
|
|
if (arg && arg->isPassedObject()) {
|
|
|
|
|
passArg = passArgIdx;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
++passArgIdx;
|
|
|
|
|
}
|
2022-11-25 21:23:50 +01:00
|
|
|
if (!passArg)
|
|
|
|
|
return passArg;
|
|
|
|
|
// Take into account result inserted as arguments.
|
|
|
|
|
if (std::optional<Fortran::lower::CallInterface<
|
|
|
|
|
Fortran::lower::CallerInterface>::PassedEntity>
|
|
|
|
|
resultArg = getPassedResult()) {
|
|
|
|
|
if (resultArg->passBy == PassEntityBy::AddressAndLength)
|
|
|
|
|
passArg = *passArg + 2;
|
|
|
|
|
else if (resultArg->passBy == PassEntityBy::BaseAddress)
|
|
|
|
|
passArg = *passArg + 1;
|
|
|
|
|
}
|
2022-10-12 15:24:21 +02:00
|
|
|
return passArg;
|
|
|
|
|
}
|
|
|
|
|
|
2022-02-23 19:48:07 +01:00
|
|
|
const Fortran::semantics::Symbol *
|
|
|
|
|
Fortran::lower::CallerInterface::getIfIndirectCallSymbol() const {
|
|
|
|
|
if (const Fortran::semantics::Symbol *symbol = procRef.proc().GetSymbol())
|
|
|
|
|
if (Fortran::semantics::IsPointer(*symbol) ||
|
|
|
|
|
Fortran::semantics::IsDummy(*symbol))
|
|
|
|
|
return symbol;
|
|
|
|
|
return nullptr;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
mlir::Location Fortran::lower::CallerInterface::getCalleeLocation() const {
|
|
|
|
|
const Fortran::evaluate::ProcedureDesignator &proc = procRef.proc();
|
|
|
|
|
// FIXME: If the callee is defined in the same file but after the current
|
|
|
|
|
// unit we cannot get its location here and the funcOp is created at the
|
|
|
|
|
// wrong location (i.e, the caller location).
|
|
|
|
|
if (const Fortran::semantics::Symbol *symbol = proc.GetSymbol())
|
|
|
|
|
return converter.genLocation(symbol->name());
|
|
|
|
|
// Use current location for intrinsics.
|
|
|
|
|
return converter.getCurrentLocation();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Get dummy argument characteristic for a procedure with implicit interface
|
|
|
|
|
// from the actual argument characteristic. The actual argument may not be a F77
|
|
|
|
|
// entity. The attribute must be dropped and the shape, if any, must be made
|
|
|
|
|
// explicit.
|
|
|
|
|
static Fortran::evaluate::characteristics::DummyDataObject
|
|
|
|
|
asImplicitArg(Fortran::evaluate::characteristics::DummyDataObject &&dummy) {
|
|
|
|
|
Fortran::evaluate::Shape shape =
|
|
|
|
|
dummy.type.attrs().none() ? dummy.type.shape()
|
|
|
|
|
: Fortran::evaluate::Shape(dummy.type.Rank());
|
|
|
|
|
return Fortran::evaluate::characteristics::DummyDataObject(
|
|
|
|
|
Fortran::evaluate::characteristics::TypeAndShape(dummy.type.type(),
|
|
|
|
|
std::move(shape)));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static Fortran::evaluate::characteristics::DummyArgument
|
|
|
|
|
asImplicitArg(Fortran::evaluate::characteristics::DummyArgument &&dummy) {
|
|
|
|
|
return std::visit(
|
|
|
|
|
Fortran::common::visitors{
|
|
|
|
|
[&](Fortran::evaluate::characteristics::DummyDataObject &obj) {
|
|
|
|
|
return Fortran::evaluate::characteristics::DummyArgument(
|
|
|
|
|
std::move(dummy.name), asImplicitArg(std::move(obj)));
|
|
|
|
|
},
|
|
|
|
|
[&](Fortran::evaluate::characteristics::DummyProcedure &proc) {
|
|
|
|
|
return Fortran::evaluate::characteristics::DummyArgument(
|
|
|
|
|
std::move(dummy.name), std::move(proc));
|
|
|
|
|
},
|
|
|
|
|
[](Fortran::evaluate::characteristics::AlternateReturn &x) {
|
|
|
|
|
return Fortran::evaluate::characteristics::DummyArgument(
|
|
|
|
|
std::move(x));
|
|
|
|
|
}},
|
|
|
|
|
dummy.u);
|
|
|
|
|
}
|
|
|
|
|
|
[flang][hlfir] Ramp-up support of implicit interface mismatches
There is a lot of Fortran code that takes advantage of F77 implicit
interface to pass arguments with a different type than those from
the subprogram definition (which is well defined if the storage
and passing convention are the same or compatible).
When the definition and calls are in different files, there is nothing
special to do: the actual arguments are already used to compute the
call interface.
The trouble for lowering comes when the definition is in the same
compilation unit (Semantics raises warning). Then, lowering will
be provided with the interface from the definition to prepare the
argument, and this leads to many ad-hoc handling (see
builder.convertWithSemantics) in the current lowering to cope
with the dummy/actual mismatches on a case by case basis. The
current lowering to FIR is not even complete for all mismatch cases that
can be found in the wild (see https://github.com/llvm/llvm-project/issues/60550),
it is crashing or hitting asserts for many of the added tests.
For HLFIR, instead of coping on a case by case basis, the call
interface will be recomputed according to the actual arguments when
calling an external procedure that can be called with an explicit
interface.
One extra case still has to be handled manually because it may happen
in calls with explicit interfaces: passing a character procedure
designator to a non character procedure dummy (and vice-versa) is widely
accepted even with explicit interfaces (and flang semantic accepts it).
Yet, this "mismatch" cannot be dealt with a simple fir.convert because
character dummy procedure are passed with a different passing
convention: an extra argument is hoisted for the result length (in FIR,
there is no extra argument yet, but the MLIR func argument is a
tuple<fir.boxproc, len>).
Differential Revision: https://reviews.llvm.org/D143636
2023-02-10 08:56:53 +01:00
|
|
|
static bool isExternalDefinedInSameCompilationUnit(
|
|
|
|
|
const Fortran::evaluate::ProcedureDesignator &proc) {
|
|
|
|
|
if (const auto *symbol{proc.GetSymbol()})
|
|
|
|
|
return symbol->has<Fortran::semantics::SubprogramDetails>() &&
|
|
|
|
|
symbol->owner().IsGlobal();
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2022-02-23 19:48:07 +01:00
|
|
|
Fortran::evaluate::characteristics::Procedure
|
|
|
|
|
Fortran::lower::CallerInterface::characterize() const {
|
|
|
|
|
Fortran::evaluate::FoldingContext &foldingContext =
|
|
|
|
|
converter.getFoldingContext();
|
|
|
|
|
std::optional<Fortran::evaluate::characteristics::Procedure> characteristic =
|
|
|
|
|
Fortran::evaluate::characteristics::Procedure::Characterize(
|
|
|
|
|
procRef.proc(), foldingContext);
|
|
|
|
|
assert(characteristic && "Failed to get characteristic from procRef");
|
|
|
|
|
// The characteristic may not contain the argument characteristic if the
|
[flang][hlfir] Ramp-up support of implicit interface mismatches
There is a lot of Fortran code that takes advantage of F77 implicit
interface to pass arguments with a different type than those from
the subprogram definition (which is well defined if the storage
and passing convention are the same or compatible).
When the definition and calls are in different files, there is nothing
special to do: the actual arguments are already used to compute the
call interface.
The trouble for lowering comes when the definition is in the same
compilation unit (Semantics raises warning). Then, lowering will
be provided with the interface from the definition to prepare the
argument, and this leads to many ad-hoc handling (see
builder.convertWithSemantics) in the current lowering to cope
with the dummy/actual mismatches on a case by case basis. The
current lowering to FIR is not even complete for all mismatch cases that
can be found in the wild (see https://github.com/llvm/llvm-project/issues/60550),
it is crashing or hitting asserts for many of the added tests.
For HLFIR, instead of coping on a case by case basis, the call
interface will be recomputed according to the actual arguments when
calling an external procedure that can be called with an explicit
interface.
One extra case still has to be handled manually because it may happen
in calls with explicit interfaces: passing a character procedure
designator to a non character procedure dummy (and vice-versa) is widely
accepted even with explicit interfaces (and flang semantic accepts it).
Yet, this "mismatch" cannot be dealt with a simple fir.convert because
character dummy procedure are passed with a different passing
convention: an extra argument is hoisted for the result length (in FIR,
there is no extra argument yet, but the MLIR func argument is a
tuple<fir.boxproc, len>).
Differential Revision: https://reviews.llvm.org/D143636
2023-02-10 08:56:53 +01:00
|
|
|
// 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();
|
2022-02-23 19:48:07 +01:00
|
|
|
for (const std::optional<Fortran::evaluate::ActualArgument> &arg :
|
|
|
|
|
procRef.arguments()) {
|
[flang][hlfir] Ramp-up support of implicit interface mismatches
There is a lot of Fortran code that takes advantage of F77 implicit
interface to pass arguments with a different type than those from
the subprogram definition (which is well defined if the storage
and passing convention are the same or compatible).
When the definition and calls are in different files, there is nothing
special to do: the actual arguments are already used to compute the
call interface.
The trouble for lowering comes when the definition is in the same
compilation unit (Semantics raises warning). Then, lowering will
be provided with the interface from the definition to prepare the
argument, and this leads to many ad-hoc handling (see
builder.convertWithSemantics) in the current lowering to cope
with the dummy/actual mismatches on a case by case basis. The
current lowering to FIR is not even complete for all mismatch cases that
can be found in the wild (see https://github.com/llvm/llvm-project/issues/60550),
it is crashing or hitting asserts for many of the added tests.
For HLFIR, instead of coping on a case by case basis, the call
interface will be recomputed according to the actual arguments when
calling an external procedure that can be called with an explicit
interface.
One extra case still has to be handled manually because it may happen
in calls with explicit interfaces: passing a character procedure
designator to a non character procedure dummy (and vice-versa) is widely
accepted even with explicit interfaces (and flang semantic accepts it).
Yet, this "mismatch" cannot be dealt with a simple fir.convert because
character dummy procedure are passed with a different passing
convention: an extra argument is hoisted for the result length (in FIR,
there is no extra argument yet, but the MLIR func argument is a
tuple<fir.boxproc, len>).
Differential Revision: https://reviews.llvm.org/D143636
2023-02-10 08:56:53 +01:00
|
|
|
// "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<Fortran::evaluate::characteristics::DummyArgument>
|
|
|
|
|
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)));
|
|
|
|
|
}
|
2022-02-23 19:48:07 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return *characteristic;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void Fortran::lower::CallerInterface::placeInput(
|
|
|
|
|
const PassedEntity &passedEntity, mlir::Value arg) {
|
|
|
|
|
assert(static_cast<int>(actualInputs.size()) > passedEntity.firArgument &&
|
|
|
|
|
passedEntity.firArgument >= 0 &&
|
|
|
|
|
passedEntity.passBy != CallInterface::PassEntityBy::AddressAndLength &&
|
|
|
|
|
"bad arg position");
|
|
|
|
|
actualInputs[passedEntity.firArgument] = arg;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void Fortran::lower::CallerInterface::placeAddressAndLengthInput(
|
|
|
|
|
const PassedEntity &passedEntity, mlir::Value addr, mlir::Value len) {
|
|
|
|
|
assert(static_cast<int>(actualInputs.size()) > passedEntity.firArgument &&
|
|
|
|
|
static_cast<int>(actualInputs.size()) > passedEntity.firLength &&
|
|
|
|
|
passedEntity.firArgument >= 0 && passedEntity.firLength >= 0 &&
|
|
|
|
|
passedEntity.passBy == CallInterface::PassEntityBy::AddressAndLength &&
|
|
|
|
|
"bad arg position");
|
|
|
|
|
actualInputs[passedEntity.firArgument] = addr;
|
|
|
|
|
actualInputs[passedEntity.firLength] = len;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool Fortran::lower::CallerInterface::verifyActualInputs() const {
|
|
|
|
|
if (getNumFIRArguments() != actualInputs.size())
|
|
|
|
|
return false;
|
|
|
|
|
for (mlir::Value arg : actualInputs) {
|
|
|
|
|
if (!arg)
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void Fortran::lower::CallerInterface::walkResultLengths(
|
|
|
|
|
ExprVisitor visitor) const {
|
|
|
|
|
assert(characteristic && "characteristic was not computed");
|
|
|
|
|
const Fortran::evaluate::characteristics::FunctionResult &result =
|
|
|
|
|
characteristic->functionResult.value();
|
|
|
|
|
const Fortran::evaluate::characteristics::TypeAndShape *typeAndShape =
|
|
|
|
|
result.GetTypeAndShape();
|
|
|
|
|
assert(typeAndShape && "no result type");
|
|
|
|
|
Fortran::evaluate::DynamicType dynamicType = typeAndShape->type();
|
|
|
|
|
// Visit result length specification expressions that are explicit.
|
|
|
|
|
if (dynamicType.category() == Fortran::common::TypeCategory::Character) {
|
|
|
|
|
if (std::optional<Fortran::evaluate::ExtentExpr> length =
|
|
|
|
|
dynamicType.GetCharLength())
|
|
|
|
|
visitor(toEvExpr(*length));
|
2022-11-25 13:47:25 +01:00
|
|
|
} else if (dynamicType.category() == common::TypeCategory::Derived &&
|
|
|
|
|
!dynamicType.IsUnlimitedPolymorphic()) {
|
2022-03-10 18:06:20 +01:00
|
|
|
const Fortran::semantics::DerivedTypeSpec &derivedTypeSpec =
|
|
|
|
|
dynamicType.GetDerivedTypeSpec();
|
|
|
|
|
if (Fortran::semantics::CountLenParameters(derivedTypeSpec) > 0)
|
|
|
|
|
TODO(converter.getCurrentLocation(),
|
|
|
|
|
"function result with derived type length parameters");
|
2022-02-23 19:48:07 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Compute extent expr from shapeSpec of an explicit shape.
|
|
|
|
|
// TODO: Allow evaluate shape analysis to work in a mode where it disregards
|
|
|
|
|
// the non-constant aspects when building the shape to avoid having this here.
|
|
|
|
|
static Fortran::evaluate::ExtentExpr
|
|
|
|
|
getExtentExpr(const Fortran::semantics::ShapeSpec &shapeSpec) {
|
|
|
|
|
const auto &ubound = shapeSpec.ubound().GetExplicit();
|
|
|
|
|
const auto &lbound = shapeSpec.lbound().GetExplicit();
|
|
|
|
|
assert(lbound && ubound && "shape must be explicit");
|
|
|
|
|
return Fortran::common::Clone(*ubound) - Fortran::common::Clone(*lbound) +
|
|
|
|
|
Fortran::evaluate::ExtentExpr{1};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void Fortran::lower::CallerInterface::walkResultExtents(
|
|
|
|
|
ExprVisitor visitor) const {
|
|
|
|
|
// Walk directly the result symbol shape (the characteristic shape may contain
|
|
|
|
|
// descriptor inquiries to it that would fail to lower on the caller side).
|
2022-03-22 15:40:32 +01:00
|
|
|
const Fortran::semantics::SubprogramDetails *interfaceDetails =
|
|
|
|
|
getInterfaceDetails();
|
|
|
|
|
if (interfaceDetails) {
|
|
|
|
|
const Fortran::semantics::Symbol &result = interfaceDetails->result();
|
2022-02-23 19:48:07 +01:00
|
|
|
if (const auto *objectDetails =
|
|
|
|
|
result.detailsIf<Fortran::semantics::ObjectEntityDetails>())
|
|
|
|
|
if (objectDetails->shape().IsExplicitShape())
|
|
|
|
|
for (const Fortran::semantics::ShapeSpec &shapeSpec :
|
|
|
|
|
objectDetails->shape())
|
|
|
|
|
visitor(Fortran::evaluate::AsGenericExpr(getExtentExpr(shapeSpec)));
|
|
|
|
|
} else {
|
|
|
|
|
if (procRef.Rank() != 0)
|
|
|
|
|
fir::emitFatalError(
|
|
|
|
|
converter.getCurrentLocation(),
|
|
|
|
|
"only scalar functions may not have an interface symbol");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool Fortran::lower::CallerInterface::mustMapInterfaceSymbols() const {
|
|
|
|
|
assert(characteristic && "characteristic was not computed");
|
|
|
|
|
const std::optional<Fortran::evaluate::characteristics::FunctionResult>
|
|
|
|
|
&result = characteristic->functionResult;
|
|
|
|
|
if (!result || result->CanBeReturnedViaImplicitInterface() ||
|
2022-03-22 15:40:32 +01:00
|
|
|
!getInterfaceDetails())
|
2022-02-23 19:48:07 +01:00
|
|
|
return false;
|
|
|
|
|
bool allResultSpecExprConstant = true;
|
|
|
|
|
auto visitor = [&](const Fortran::lower::SomeExpr &e) {
|
|
|
|
|
allResultSpecExprConstant &= Fortran::evaluate::IsConstantExpr(e);
|
|
|
|
|
};
|
|
|
|
|
walkResultLengths(visitor);
|
|
|
|
|
walkResultExtents(visitor);
|
|
|
|
|
return !allResultSpecExprConstant;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
mlir::Value Fortran::lower::CallerInterface::getArgumentValue(
|
|
|
|
|
const semantics::Symbol &sym) const {
|
|
|
|
|
mlir::Location loc = converter.getCurrentLocation();
|
2022-03-22 15:40:32 +01:00
|
|
|
const Fortran::semantics::SubprogramDetails *ifaceDetails =
|
|
|
|
|
getInterfaceDetails();
|
|
|
|
|
if (!ifaceDetails)
|
2022-02-23 19:48:07 +01:00
|
|
|
fir::emitFatalError(
|
|
|
|
|
loc, "mapping actual and dummy arguments requires an interface");
|
|
|
|
|
const std::vector<Fortran::semantics::Symbol *> &dummies =
|
2022-03-22 15:40:32 +01:00
|
|
|
ifaceDetails->dummyArgs();
|
2022-02-23 19:48:07 +01:00
|
|
|
auto it = std::find(dummies.begin(), dummies.end(), &sym);
|
|
|
|
|
if (it == dummies.end())
|
|
|
|
|
fir::emitFatalError(loc, "symbol is not a dummy in this call");
|
|
|
|
|
FirValue mlirArgIndex = passedArguments[it - dummies.begin()].firArgument;
|
|
|
|
|
return actualInputs[mlirArgIndex];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
mlir::Type Fortran::lower::CallerInterface::getResultStorageType() const {
|
|
|
|
|
if (passedResult)
|
|
|
|
|
return fir::dyn_cast_ptrEleTy(inputs[passedResult->firArgument].type);
|
|
|
|
|
assert(saveResult && !outputs.empty());
|
|
|
|
|
return outputs[0].type;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const Fortran::semantics::Symbol &
|
|
|
|
|
Fortran::lower::CallerInterface::getResultSymbol() const {
|
|
|
|
|
mlir::Location loc = converter.getCurrentLocation();
|
2022-03-22 15:40:32 +01:00
|
|
|
const Fortran::semantics::SubprogramDetails *ifaceDetails =
|
|
|
|
|
getInterfaceDetails();
|
|
|
|
|
if (!ifaceDetails)
|
2022-02-23 19:48:07 +01:00
|
|
|
fir::emitFatalError(
|
|
|
|
|
loc, "mapping actual and dummy arguments requires an interface");
|
2022-03-22 15:40:32 +01:00
|
|
|
return ifaceDetails->result();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const Fortran::semantics::SubprogramDetails *
|
|
|
|
|
Fortran::lower::CallerInterface::getInterfaceDetails() const {
|
|
|
|
|
if (const Fortran::semantics::Symbol *iface =
|
|
|
|
|
procRef.proc().GetInterfaceSymbol())
|
|
|
|
|
return iface->GetUltimate()
|
|
|
|
|
.detailsIf<Fortran::semantics::SubprogramDetails>();
|
|
|
|
|
return nullptr;
|
2022-02-23 19:48:07 +01:00
|
|
|
}
|
|
|
|
|
|
2022-01-28 22:39:44 +01:00
|
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
|
// Callee side interface implementation
|
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
|
|
2022-02-14 21:31:46 +01:00
|
|
|
bool Fortran::lower::CalleeInterface::hasAlternateReturns() const {
|
|
|
|
|
return !funit.isMainProgram() &&
|
|
|
|
|
Fortran::semantics::HasAlternateReturns(funit.getSubprogramSymbol());
|
|
|
|
|
}
|
|
|
|
|
|
2022-01-28 22:39:44 +01:00
|
|
|
std::string Fortran::lower::CalleeInterface::getMangledName() const {
|
|
|
|
|
if (funit.isMainProgram())
|
|
|
|
|
return fir::NameUniquer::doProgramEntry().str();
|
[flang] Block construct
A block construct is an execution control construct that supports
declaration scopes contained within a parent subprogram scope or another
block scope. (blocks may be nested.) This is implemented by applying
basic scope processing to the block level.
Name uniquing/mangling is extended to support this. The term "block" is
heavily overloaded in Fortran standards. Prior name uniquing used tag `B`
for common block objects. Existing tag choices were modified to free up `B`
for block construct entities, and `C` for common blocks, and resolve
additional issues with other tags. The "old tag -> new tag" changes can
be summarized as:
-> B -- block construct -> new
B -> C -- common block
C -> YI -- intrinsic type descriptor; not currently generated
CT -> Y -- nonintrinsic type descriptor; not currently generated
G -> N -- namelist group
L -> -- block data; not needed -> deleted
Existing name uniquing components consist of a tag followed by a name
from user source code, such as a module, subprogram, or variable name.
Block constructs are different in that they may be anonymous. (Like other
constructs, a block may have a `block-construct-name` that can be used
in exit statements, but this name is optional.) So blocks are given a
numeric compiler-generated preorder index starting with `B1`, `B2`,
and so on, on a per-procedure basis.
Name uniquing is also modified to include component names for all
containing procedures rather than for just the immediate host. This
fixes an existing name clash bug with same-named entities in same-named
host subprograms contained in different-named containing subprograms,
and variations of the bug involving modules and submodules.
F18 clause 9.7.3.1 (Deallocation of allocatable variables) paragraph 1
has a requirement that an allocated, unsaved allocatable local variable
must be deallocated on procedure exit. The following paragraph 2 states:
When a BLOCK construct terminates, any unsaved allocated allocatable
local variable of the construct is deallocated.
Similarly, F18 clause 7.5.6.3 (When finalization occurs) paragraph 3
has a requirement that a nonpointer, nonallocatable object must be
finalized on procedure exit. The following paragraph 4 states:
A nonpointer nonallocatable local variable of a BLOCK construct
is finalized immediately before it would become undefined due to
termination of the BLOCK construct.
These deallocation and finalization requirements, along with stack
restoration requirements, require knowledge of block exits. In addition
to normal block termination at an end-block-stmt, a block may be
terminated by executing a branching statement that targets a statement
outside of the block. This includes
Single-target branch statements:
- goto
- exit
- cycle
- return
Bounded multiple-target branch statements:
- arithmetic goto
- IO statement with END, EOR, or ERR specifiers
Unbounded multiple-target branch statements:
- call with alternate return specs
- computed goto
- assigned goto
Lowering code is extended to determine if one of these branches exits
one or more relevant blocks or other constructs, and adds a mechanism to
insert any necessary deallocation, finalization, or stack restoration
code at the source of the branch. For a single-target branch it suffices
to generate the exit code just prior to taking the indicated branch.
Each target of a multiple-target branch must be analyzed individually.
Where necessary, the code must first branch to an intermediate basic
block that contains exit code, followed by a branch to the original target
statement.
This patch implements an `activeConstructStack` construct exit mechanism
that queries a new `activeConstruct` PFT bit to insert stack restoration
code at block exits. It ties in to existing code in ConvertVariable.cpp
routine `instantiateLocal` which has code for finalization, making block
exit finalization on par with subprogram exit finalization. Deallocation
is as yet unimplemented for subprograms or blocks. This may result in
memory leaks for affected objects at either the subprogram or block level.
Deallocation cases can be addressed uniformly for both scopes in a future
patch, presumably with code insertion in routine `instantiateLocal`.
The exit code mechanism is not limited to block construct exits. It is
also available for use with other constructs. In particular, it is used
to replace custom deallocation code for a select case construct character
selector expression where applicable. This functionality is also added
to select type and associate constructs. It is available for use with
other constructs, such as select rank and image control constructs,
if that turns out to be necessary.
Overlapping nonfunctional changes include eliminating "FIR" from some
routine names and eliminating obsolete spaces in comments.
2023-02-27 14:05:53 -08:00
|
|
|
return ::getMangledName(converter, funit.getSubprogramSymbol());
|
2022-01-28 22:39:44 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const Fortran::semantics::Symbol *
|
|
|
|
|
Fortran::lower::CalleeInterface::getProcedureSymbol() const {
|
|
|
|
|
if (funit.isMainProgram())
|
2023-02-17 12:44:33 -08:00
|
|
|
return funit.getMainProgramSymbol();
|
2022-01-28 22:39:44 +01:00
|
|
|
return &funit.getSubprogramSymbol();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
mlir::Location Fortran::lower::CalleeInterface::getCalleeLocation() const {
|
|
|
|
|
// FIXME: do NOT use unknown for the anonymous PROGRAM case. We probably
|
|
|
|
|
// should just stash the location in the funit regardless.
|
|
|
|
|
return converter.genLocation(funit.getStartingSourceLoc());
|
|
|
|
|
}
|
|
|
|
|
|
2022-02-14 21:31:46 +01:00
|
|
|
Fortran::evaluate::characteristics::Procedure
|
|
|
|
|
Fortran::lower::CalleeInterface::characterize() const {
|
|
|
|
|
Fortran::evaluate::FoldingContext &foldingContext =
|
|
|
|
|
converter.getFoldingContext();
|
|
|
|
|
std::optional<Fortran::evaluate::characteristics::Procedure> characteristic =
|
|
|
|
|
Fortran::evaluate::characteristics::Procedure::Characterize(
|
|
|
|
|
funit.getSubprogramSymbol(), foldingContext);
|
|
|
|
|
assert(characteristic && "Fail to get characteristic from symbol");
|
|
|
|
|
return *characteristic;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool Fortran::lower::CalleeInterface::isMainProgram() const {
|
|
|
|
|
return funit.isMainProgram();
|
|
|
|
|
}
|
|
|
|
|
|
2022-04-18 11:53:47 -07:00
|
|
|
mlir::func::FuncOp
|
|
|
|
|
Fortran::lower::CalleeInterface::addEntryBlockAndMapArguments() {
|
2022-06-22 20:46:30 +02:00
|
|
|
// Check for bugs in the front end. The front end must not present multiple
|
|
|
|
|
// definitions of the same procedure.
|
|
|
|
|
if (!func.getBlocks().empty())
|
|
|
|
|
fir::emitFatalError(func.getLoc(),
|
|
|
|
|
"cannot process subprogram that was already processed");
|
|
|
|
|
|
|
|
|
|
// On the callee side, directly map the mlir::value argument of the function
|
|
|
|
|
// block to the Fortran symbols.
|
2022-01-28 22:39:44 +01:00
|
|
|
func.addEntryBlock();
|
2022-02-16 20:27:23 +01:00
|
|
|
mapPassedEntities();
|
2022-01-28 22:39:44 +01:00
|
|
|
return func;
|
|
|
|
|
}
|
|
|
|
|
|
2022-03-07 19:55:48 +01:00
|
|
|
bool Fortran::lower::CalleeInterface::hasHostAssociated() const {
|
2022-12-20 13:49:38 +01:00
|
|
|
return funit.parentHasTupleHostAssoc();
|
2022-03-07 19:55:48 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
mlir::Type Fortran::lower::CalleeInterface::getHostAssociatedTy() const {
|
|
|
|
|
assert(hasHostAssociated());
|
|
|
|
|
return funit.parentHostAssoc().getArgumentType(converter);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
mlir::Value Fortran::lower::CalleeInterface::getHostAssociatedTuple() const {
|
|
|
|
|
assert(hasHostAssociated() || !funit.getHostAssoc().empty());
|
|
|
|
|
return converter.hostAssocTupleValue();
|
|
|
|
|
}
|
|
|
|
|
|
2022-12-20 13:49:38 +01:00
|
|
|
void Fortran::lower::CalleeInterface::setFuncAttrs(
|
|
|
|
|
mlir::func::FuncOp func) const {
|
|
|
|
|
if (funit.parentHasHostAssoc())
|
|
|
|
|
func->setAttr(fir::getInternalProcedureAttrName(),
|
|
|
|
|
mlir::UnitAttr::get(func->getContext()));
|
|
|
|
|
}
|
|
|
|
|
|
2022-01-28 22:39:44 +01:00
|
|
|
//===----------------------------------------------------------------------===//
|
[flang] Block construct
A block construct is an execution control construct that supports
declaration scopes contained within a parent subprogram scope or another
block scope. (blocks may be nested.) This is implemented by applying
basic scope processing to the block level.
Name uniquing/mangling is extended to support this. The term "block" is
heavily overloaded in Fortran standards. Prior name uniquing used tag `B`
for common block objects. Existing tag choices were modified to free up `B`
for block construct entities, and `C` for common blocks, and resolve
additional issues with other tags. The "old tag -> new tag" changes can
be summarized as:
-> B -- block construct -> new
B -> C -- common block
C -> YI -- intrinsic type descriptor; not currently generated
CT -> Y -- nonintrinsic type descriptor; not currently generated
G -> N -- namelist group
L -> -- block data; not needed -> deleted
Existing name uniquing components consist of a tag followed by a name
from user source code, such as a module, subprogram, or variable name.
Block constructs are different in that they may be anonymous. (Like other
constructs, a block may have a `block-construct-name` that can be used
in exit statements, but this name is optional.) So blocks are given a
numeric compiler-generated preorder index starting with `B1`, `B2`,
and so on, on a per-procedure basis.
Name uniquing is also modified to include component names for all
containing procedures rather than for just the immediate host. This
fixes an existing name clash bug with same-named entities in same-named
host subprograms contained in different-named containing subprograms,
and variations of the bug involving modules and submodules.
F18 clause 9.7.3.1 (Deallocation of allocatable variables) paragraph 1
has a requirement that an allocated, unsaved allocatable local variable
must be deallocated on procedure exit. The following paragraph 2 states:
When a BLOCK construct terminates, any unsaved allocated allocatable
local variable of the construct is deallocated.
Similarly, F18 clause 7.5.6.3 (When finalization occurs) paragraph 3
has a requirement that a nonpointer, nonallocatable object must be
finalized on procedure exit. The following paragraph 4 states:
A nonpointer nonallocatable local variable of a BLOCK construct
is finalized immediately before it would become undefined due to
termination of the BLOCK construct.
These deallocation and finalization requirements, along with stack
restoration requirements, require knowledge of block exits. In addition
to normal block termination at an end-block-stmt, a block may be
terminated by executing a branching statement that targets a statement
outside of the block. This includes
Single-target branch statements:
- goto
- exit
- cycle
- return
Bounded multiple-target branch statements:
- arithmetic goto
- IO statement with END, EOR, or ERR specifiers
Unbounded multiple-target branch statements:
- call with alternate return specs
- computed goto
- assigned goto
Lowering code is extended to determine if one of these branches exits
one or more relevant blocks or other constructs, and adds a mechanism to
insert any necessary deallocation, finalization, or stack restoration
code at the source of the branch. For a single-target branch it suffices
to generate the exit code just prior to taking the indicated branch.
Each target of a multiple-target branch must be analyzed individually.
Where necessary, the code must first branch to an intermediate basic
block that contains exit code, followed by a branch to the original target
statement.
This patch implements an `activeConstructStack` construct exit mechanism
that queries a new `activeConstruct` PFT bit to insert stack restoration
code at block exits. It ties in to existing code in ConvertVariable.cpp
routine `instantiateLocal` which has code for finalization, making block
exit finalization on par with subprogram exit finalization. Deallocation
is as yet unimplemented for subprograms or blocks. This may result in
memory leaks for affected objects at either the subprogram or block level.
Deallocation cases can be addressed uniformly for both scopes in a future
patch, presumably with code insertion in routine `instantiateLocal`.
The exit code mechanism is not limited to block construct exits. It is
also available for use with other constructs. In particular, it is used
to replace custom deallocation code for a select case construct character
selector expression where applicable. This functionality is also added
to select type and associate constructs. It is available for use with
other constructs, such as select rank and image control constructs,
if that turns out to be necessary.
Overlapping nonfunctional changes include eliminating "FIR" from some
routine names and eliminating obsolete spaces in comments.
2023-02-27 14:05:53 -08:00
|
|
|
// CallInterface implementation: this part is common to both caller and callee.
|
2022-01-28 22:39:44 +01:00
|
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
|
|
2022-04-18 11:53:47 -07:00
|
|
|
static void addSymbolAttribute(mlir::func::FuncOp func,
|
2022-01-28 22:39:44 +01:00
|
|
|
const Fortran::semantics::Symbol &sym,
|
|
|
|
|
mlir::MLIRContext &mlirContext) {
|
|
|
|
|
// Only add this on bind(C) functions for which the symbol is not reflected in
|
|
|
|
|
// the current context.
|
|
|
|
|
if (!Fortran::semantics::IsBindCProcedure(sym))
|
|
|
|
|
return;
|
|
|
|
|
std::string name =
|
|
|
|
|
Fortran::lower::mangle::mangleName(sym, /*keepExternalInScope=*/true);
|
|
|
|
|
func->setAttr(fir::getSymbolAttrName(),
|
|
|
|
|
mlir::StringAttr::get(&mlirContext, name));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Declare drives the different actions to be performed while analyzing the
|
2022-04-18 11:53:47 -07:00
|
|
|
/// signature and building/finding the mlir::func::FuncOp.
|
2022-01-28 22:39:44 +01:00
|
|
|
template <typename T>
|
|
|
|
|
void Fortran::lower::CallInterface<T>::declare() {
|
2022-02-14 21:31:46 +01:00
|
|
|
if (!side().isMainProgram()) {
|
|
|
|
|
characteristic.emplace(side().characterize());
|
|
|
|
|
bool isImplicit = characteristic->CanBeCalledViaImplicitInterface();
|
|
|
|
|
determineInterface(isImplicit, *characteristic);
|
|
|
|
|
}
|
|
|
|
|
// No input/output for main program
|
|
|
|
|
|
2022-01-28 22:39:44 +01:00
|
|
|
// Create / get funcOp for direct calls. For indirect calls (only meaningful
|
|
|
|
|
// on the caller side), no funcOp has to be created here. The mlir::Value
|
|
|
|
|
// holding the indirection is used when creating the fir::CallOp.
|
|
|
|
|
if (!side().isIndirectCall()) {
|
|
|
|
|
std::string name = side().getMangledName();
|
|
|
|
|
mlir::ModuleOp module = converter.getModuleOp();
|
|
|
|
|
func = fir::FirOpBuilder::getNamedFunction(module, name);
|
|
|
|
|
if (!func) {
|
|
|
|
|
mlir::Location loc = side().getCalleeLocation();
|
|
|
|
|
mlir::FunctionType ty = genFunctionType();
|
|
|
|
|
func = fir::FirOpBuilder::createFunction(loc, module, name, ty);
|
2023-02-17 12:44:33 -08:00
|
|
|
if (const Fortran::semantics::Symbol *sym = side().getProcedureSymbol()) {
|
|
|
|
|
if (side().isMainProgram()) {
|
|
|
|
|
func->setAttr(fir::getSymbolAttrName(),
|
|
|
|
|
mlir::StringAttr::get(&converter.getMLIRContext(),
|
|
|
|
|
sym->name().ToString()));
|
|
|
|
|
} else {
|
|
|
|
|
addSymbolAttribute(func, *sym, converter.getMLIRContext());
|
|
|
|
|
}
|
|
|
|
|
}
|
2022-02-16 20:27:23 +01:00
|
|
|
for (const auto &placeHolder : llvm::enumerate(inputs))
|
|
|
|
|
if (!placeHolder.value().attributes.empty())
|
|
|
|
|
func.setArgAttrs(placeHolder.index(), placeHolder.value().attributes);
|
2022-12-20 13:49:38 +01:00
|
|
|
side().setFuncAttrs(func);
|
2022-01-28 22:39:44 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-04-18 11:53:47 -07:00
|
|
|
/// Once the signature has been analyzed and the mlir::func::FuncOp was
|
|
|
|
|
/// built/found, map the fir inputs to Fortran entities (the symbols or
|
|
|
|
|
/// expressions).
|
2022-02-16 20:27:23 +01:00
|
|
|
template <typename T>
|
|
|
|
|
void Fortran::lower::CallInterface<T>::mapPassedEntities() {
|
|
|
|
|
// map back fir inputs to passed entities
|
|
|
|
|
if constexpr (std::is_same_v<T, Fortran::lower::CalleeInterface>) {
|
|
|
|
|
assert(inputs.size() == func.front().getArguments().size() &&
|
|
|
|
|
"function previously created with different number of arguments");
|
|
|
|
|
for (auto [fst, snd] : llvm::zip(inputs, func.front().getArguments()))
|
|
|
|
|
mapBackInputToPassedEntity(fst, snd);
|
|
|
|
|
} else {
|
|
|
|
|
// On the caller side, map the index of the mlir argument position
|
|
|
|
|
// to Fortran ActualArguments.
|
|
|
|
|
int firPosition = 0;
|
|
|
|
|
for (const FirPlaceHolder &placeHolder : inputs)
|
|
|
|
|
mapBackInputToPassedEntity(placeHolder, firPosition++);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
template <typename T>
|
|
|
|
|
void Fortran::lower::CallInterface<T>::mapBackInputToPassedEntity(
|
|
|
|
|
const FirPlaceHolder &placeHolder, FirValue firValue) {
|
|
|
|
|
PassedEntity &passedEntity =
|
|
|
|
|
placeHolder.passedEntityPosition == FirPlaceHolder::resultEntityPosition
|
|
|
|
|
? passedResult.value()
|
|
|
|
|
: passedArguments[placeHolder.passedEntityPosition];
|
|
|
|
|
if (placeHolder.property == Property::CharLength)
|
|
|
|
|
passedEntity.firLength = firValue;
|
|
|
|
|
else
|
|
|
|
|
passedEntity.firArgument = firValue;
|
|
|
|
|
}
|
|
|
|
|
|
2022-02-23 19:48:07 +01:00
|
|
|
/// Helpers to access ActualArgument/Symbols
|
|
|
|
|
static const Fortran::evaluate::ActualArguments &
|
|
|
|
|
getEntityContainer(const Fortran::evaluate::ProcedureRef &proc) {
|
|
|
|
|
return proc.arguments();
|
|
|
|
|
}
|
|
|
|
|
|
2022-02-16 20:27:23 +01:00
|
|
|
static const std::vector<Fortran::semantics::Symbol *> &
|
|
|
|
|
getEntityContainer(Fortran::lower::pft::FunctionLikeUnit &funit) {
|
|
|
|
|
return funit.getSubprogramSymbol()
|
|
|
|
|
.get<Fortran::semantics::SubprogramDetails>()
|
|
|
|
|
.dummyArgs();
|
|
|
|
|
}
|
|
|
|
|
|
2022-02-23 19:48:07 +01:00
|
|
|
static const Fortran::evaluate::ActualArgument *getDataObjectEntity(
|
|
|
|
|
const std::optional<Fortran::evaluate::ActualArgument> &arg) {
|
|
|
|
|
if (arg)
|
|
|
|
|
return &*arg;
|
|
|
|
|
return nullptr;
|
|
|
|
|
}
|
|
|
|
|
|
2022-02-16 20:27:23 +01:00
|
|
|
static const Fortran::semantics::Symbol &
|
|
|
|
|
getDataObjectEntity(const Fortran::semantics::Symbol *arg) {
|
|
|
|
|
assert(arg && "expect symbol for data object entity");
|
|
|
|
|
return *arg;
|
|
|
|
|
}
|
|
|
|
|
|
2022-02-25 18:21:44 +01:00
|
|
|
static const Fortran::evaluate::ActualArgument *
|
|
|
|
|
getResultEntity(const Fortran::evaluate::ProcedureRef &) {
|
|
|
|
|
return nullptr;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static const Fortran::semantics::Symbol &
|
|
|
|
|
getResultEntity(Fortran::lower::pft::FunctionLikeUnit &funit) {
|
|
|
|
|
return funit.getSubprogramSymbol()
|
|
|
|
|
.get<Fortran::semantics::SubprogramDetails>()
|
|
|
|
|
.result();
|
|
|
|
|
}
|
|
|
|
|
|
2022-03-07 19:55:48 +01:00
|
|
|
/// Bypass helpers to manipulate entities since they are not any symbol/actual
|
|
|
|
|
/// argument to associate. See SignatureBuilder below.
|
|
|
|
|
using FakeEntity = bool;
|
|
|
|
|
using FakeEntities = llvm::SmallVector<FakeEntity>;
|
|
|
|
|
static FakeEntities
|
|
|
|
|
getEntityContainer(const Fortran::evaluate::characteristics::Procedure &proc) {
|
|
|
|
|
FakeEntities enities(proc.dummyArguments.size());
|
|
|
|
|
return enities;
|
|
|
|
|
}
|
|
|
|
|
static const FakeEntity &getDataObjectEntity(const FakeEntity &e) { return e; }
|
|
|
|
|
static FakeEntity
|
|
|
|
|
getResultEntity(const Fortran::evaluate::characteristics::Procedure &proc) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
2022-02-14 21:31:46 +01:00
|
|
|
|
|
|
|
|
/// This is the actual part that defines the FIR interface based on the
|
|
|
|
|
/// characteristic. It directly mutates the CallInterface members.
|
|
|
|
|
template <typename T>
|
|
|
|
|
class Fortran::lower::CallInterfaceImpl {
|
|
|
|
|
using CallInterface = Fortran::lower::CallInterface<T>;
|
2022-02-16 20:27:23 +01:00
|
|
|
using PassEntityBy = typename CallInterface::PassEntityBy;
|
|
|
|
|
using PassedEntity = typename CallInterface::PassedEntity;
|
2022-02-25 18:21:44 +01:00
|
|
|
using FirValue = typename CallInterface::FirValue;
|
2022-02-16 20:27:23 +01:00
|
|
|
using FortranEntity = typename CallInterface::FortranEntity;
|
2022-02-14 21:31:46 +01:00
|
|
|
using FirPlaceHolder = typename CallInterface::FirPlaceHolder;
|
|
|
|
|
using Property = typename CallInterface::Property;
|
|
|
|
|
using TypeAndShape = Fortran::evaluate::characteristics::TypeAndShape;
|
2022-02-16 20:27:23 +01:00
|
|
|
using DummyCharacteristics =
|
|
|
|
|
Fortran::evaluate::characteristics::DummyArgument;
|
2022-02-14 21:31:46 +01:00
|
|
|
|
|
|
|
|
public:
|
|
|
|
|
CallInterfaceImpl(CallInterface &i)
|
|
|
|
|
: interface(i), mlirContext{i.converter.getMLIRContext()} {}
|
|
|
|
|
|
|
|
|
|
void buildImplicitInterface(
|
|
|
|
|
const Fortran::evaluate::characteristics::Procedure &procedure) {
|
|
|
|
|
// Handle result
|
|
|
|
|
if (const std::optional<Fortran::evaluate::characteristics::FunctionResult>
|
|
|
|
|
&result = procedure.functionResult)
|
2022-09-24 08:58:50 +02:00
|
|
|
handleImplicitResult(*result, procedure.IsBindC());
|
2022-02-14 21:31:46 +01:00
|
|
|
else if (interface.side().hasAlternateReturns())
|
|
|
|
|
addFirResult(mlir::IndexType::get(&mlirContext),
|
|
|
|
|
FirPlaceHolder::resultEntityPosition, Property::Value);
|
2022-02-16 20:27:23 +01:00
|
|
|
// Handle arguments
|
|
|
|
|
const auto &argumentEntities =
|
|
|
|
|
getEntityContainer(interface.side().getCallDescription());
|
|
|
|
|
for (auto pair : llvm::zip(procedure.dummyArguments, argumentEntities)) {
|
|
|
|
|
const Fortran::evaluate::characteristics::DummyArgument
|
|
|
|
|
&argCharacteristics = std::get<0>(pair);
|
|
|
|
|
std::visit(
|
|
|
|
|
Fortran::common::visitors{
|
|
|
|
|
[&](const auto &dummy) {
|
|
|
|
|
const auto &entity = getDataObjectEntity(std::get<1>(pair));
|
|
|
|
|
handleImplicitDummy(&argCharacteristics, dummy, entity);
|
|
|
|
|
},
|
|
|
|
|
[&](const Fortran::evaluate::characteristics::AlternateReturn &) {
|
|
|
|
|
// nothing to do
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
argCharacteristics.u);
|
|
|
|
|
}
|
2022-02-14 21:31:46 +01:00
|
|
|
}
|
|
|
|
|
|
2022-02-15 16:00:28 +01:00
|
|
|
void buildExplicitInterface(
|
|
|
|
|
const Fortran::evaluate::characteristics::Procedure &procedure) {
|
2022-09-24 08:58:50 +02:00
|
|
|
bool isBindC = procedure.IsBindC();
|
2022-02-15 16:00:28 +01:00
|
|
|
// Handle result
|
|
|
|
|
if (const std::optional<Fortran::evaluate::characteristics::FunctionResult>
|
|
|
|
|
&result = procedure.functionResult) {
|
|
|
|
|
if (result->CanBeReturnedViaImplicitInterface())
|
2022-09-24 08:58:50 +02:00
|
|
|
handleImplicitResult(*result, isBindC);
|
2022-02-15 16:00:28 +01:00
|
|
|
else
|
|
|
|
|
handleExplicitResult(*result);
|
|
|
|
|
} else if (interface.side().hasAlternateReturns()) {
|
|
|
|
|
addFirResult(mlir::IndexType::get(&mlirContext),
|
|
|
|
|
FirPlaceHolder::resultEntityPosition, Property::Value);
|
|
|
|
|
}
|
2022-02-24 17:16:02 +01:00
|
|
|
// Handle arguments
|
|
|
|
|
const auto &argumentEntities =
|
|
|
|
|
getEntityContainer(interface.side().getCallDescription());
|
|
|
|
|
for (auto pair : llvm::zip(procedure.dummyArguments, argumentEntities)) {
|
|
|
|
|
const Fortran::evaluate::characteristics::DummyArgument
|
|
|
|
|
&argCharacteristics = std::get<0>(pair);
|
|
|
|
|
std::visit(
|
|
|
|
|
Fortran::common::visitors{
|
|
|
|
|
[&](const Fortran::evaluate::characteristics::DummyDataObject
|
|
|
|
|
&dummy) {
|
|
|
|
|
const auto &entity = getDataObjectEntity(std::get<1>(pair));
|
|
|
|
|
if (dummy.CanBePassedViaImplicitInterface())
|
|
|
|
|
handleImplicitDummy(&argCharacteristics, dummy, entity);
|
|
|
|
|
else
|
|
|
|
|
handleExplicitDummy(&argCharacteristics, dummy, entity,
|
|
|
|
|
isBindC);
|
|
|
|
|
},
|
|
|
|
|
[&](const Fortran::evaluate::characteristics::DummyProcedure
|
|
|
|
|
&dummy) {
|
|
|
|
|
const auto &entity = getDataObjectEntity(std::get<1>(pair));
|
|
|
|
|
handleImplicitDummy(&argCharacteristics, dummy, entity);
|
|
|
|
|
},
|
|
|
|
|
[&](const Fortran::evaluate::characteristics::AlternateReturn &) {
|
|
|
|
|
// nothing to do
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
argCharacteristics.u);
|
|
|
|
|
}
|
2022-02-15 16:00:28 +01:00
|
|
|
}
|
|
|
|
|
|
2022-03-07 19:55:48 +01:00
|
|
|
void appendHostAssocTupleArg(mlir::Type tupTy) {
|
2022-03-03 13:25:09 +00:00
|
|
|
mlir::MLIRContext *ctxt = tupTy.getContext();
|
2022-03-07 19:55:48 +01:00
|
|
|
addFirOperand(tupTy, nextPassedArgPosition(), Property::BaseAddress,
|
|
|
|
|
{mlir::NamedAttribute{
|
|
|
|
|
mlir::StringAttr::get(ctxt, fir::getHostAssocAttrName()),
|
|
|
|
|
mlir::UnitAttr::get(ctxt)}});
|
|
|
|
|
interface.passedArguments.emplace_back(
|
|
|
|
|
PassedEntity{PassEntityBy::BaseAddress, std::nullopt,
|
|
|
|
|
interface.side().getHostAssociatedTuple(), emptyValue()});
|
|
|
|
|
}
|
|
|
|
|
|
2023-01-07 22:26:48 -08:00
|
|
|
static std::optional<Fortran::evaluate::DynamicType> getResultDynamicType(
|
2022-03-07 19:55:48 +01:00
|
|
|
const Fortran::evaluate::characteristics::Procedure &procedure) {
|
|
|
|
|
if (const std::optional<Fortran::evaluate::characteristics::FunctionResult>
|
|
|
|
|
&result = procedure.functionResult)
|
|
|
|
|
if (const auto *resultTypeAndShape = result->GetTypeAndShape())
|
|
|
|
|
return resultTypeAndShape->type();
|
2022-12-03 12:14:21 -08:00
|
|
|
return std::nullopt;
|
2022-03-07 19:55:48 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static bool mustPassLengthWithDummyProcedure(
|
|
|
|
|
const Fortran::evaluate::characteristics::Procedure &procedure) {
|
|
|
|
|
// When passing a character function designator `bar` as dummy procedure to
|
|
|
|
|
// `foo` (e.g. `foo(bar)`), pass the result length of `bar` to `foo` so that
|
|
|
|
|
// `bar` can be called inside `foo` even if its length is assumed there.
|
|
|
|
|
// From an ABI perspective, the extra length argument must be handled
|
|
|
|
|
// exactly as if passing a character object. Using an argument of
|
|
|
|
|
// fir.boxchar type gives the expected behavior: after codegen, the
|
|
|
|
|
// fir.boxchar lengths are added after all the arguments as extra value
|
|
|
|
|
// arguments (the extra arguments order is the order of the fir.boxchar).
|
|
|
|
|
|
|
|
|
|
// This ABI is compatible with ifort, nag, nvfortran, and xlf, but not
|
|
|
|
|
// gfortran. Gfortran does not pass the length and is therefore unable to
|
|
|
|
|
// handle later call to `bar` in `foo` where the length would be assumed. If
|
|
|
|
|
// the result is an array, nag and ifort and xlf still pass the length, but
|
|
|
|
|
// not nvfortran (and gfortran). It is not clear it is possible to call an
|
|
|
|
|
// array function with assumed length (f18 forbides defining such
|
|
|
|
|
// interfaces). Hence, passing the length is most likely useless, but stick
|
|
|
|
|
// with ifort/nag/xlf interface here.
|
2023-01-07 22:26:48 -08:00
|
|
|
if (std::optional<Fortran::evaluate::DynamicType> type =
|
2022-03-07 19:55:48 +01:00
|
|
|
getResultDynamicType(procedure))
|
|
|
|
|
return type->category() == Fortran::common::TypeCategory::Character;
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2022-02-14 21:31:46 +01:00
|
|
|
private:
|
|
|
|
|
void handleImplicitResult(
|
2022-09-24 08:58:50 +02:00
|
|
|
const Fortran::evaluate::characteristics::FunctionResult &result,
|
|
|
|
|
bool isBindC) {
|
2022-02-14 21:31:46 +01:00
|
|
|
if (result.IsProcedurePointer())
|
|
|
|
|
TODO(interface.converter.getCurrentLocation(),
|
|
|
|
|
"procedure pointer result not yet handled");
|
|
|
|
|
const Fortran::evaluate::characteristics::TypeAndShape *typeAndShape =
|
|
|
|
|
result.GetTypeAndShape();
|
|
|
|
|
assert(typeAndShape && "expect type for non proc pointer result");
|
|
|
|
|
Fortran::evaluate::DynamicType dynamicType = typeAndShape->type();
|
2022-02-25 18:21:44 +01:00
|
|
|
// Character result allocated by caller and passed as hidden arguments
|
2022-02-14 21:31:46 +01:00
|
|
|
if (dynamicType.category() == Fortran::common::TypeCategory::Character) {
|
2022-09-24 08:58:50 +02:00
|
|
|
if (isBindC) {
|
|
|
|
|
mlir::Type mlirType = translateDynamicType(dynamicType);
|
|
|
|
|
addFirResult(mlirType, FirPlaceHolder::resultEntityPosition,
|
|
|
|
|
Property::Value);
|
|
|
|
|
} else {
|
|
|
|
|
handleImplicitCharacterResult(dynamicType);
|
|
|
|
|
}
|
2022-02-14 21:31:46 +01:00
|
|
|
} else if (dynamicType.category() ==
|
|
|
|
|
Fortran::common::TypeCategory::Derived) {
|
2022-03-07 19:55:48 +01:00
|
|
|
// Derived result need to be allocated by the caller and the result value
|
|
|
|
|
// must be saved. Derived type in implicit interface cannot have length
|
|
|
|
|
// parameters.
|
|
|
|
|
setSaveResult();
|
|
|
|
|
mlir::Type mlirType = translateDynamicType(dynamicType);
|
|
|
|
|
addFirResult(mlirType, FirPlaceHolder::resultEntityPosition,
|
|
|
|
|
Property::Value);
|
2022-02-14 21:31:46 +01:00
|
|
|
} else {
|
|
|
|
|
// All result other than characters/derived are simply returned by value
|
|
|
|
|
// in implicit interfaces
|
|
|
|
|
mlir::Type mlirType =
|
|
|
|
|
getConverter().genType(dynamicType.category(), dynamicType.kind());
|
|
|
|
|
addFirResult(mlirType, FirPlaceHolder::resultEntityPosition,
|
|
|
|
|
Property::Value);
|
|
|
|
|
}
|
|
|
|
|
}
|
2022-02-25 18:21:44 +01:00
|
|
|
void
|
|
|
|
|
handleImplicitCharacterResult(const Fortran::evaluate::DynamicType &type) {
|
|
|
|
|
int resultPosition = FirPlaceHolder::resultEntityPosition;
|
|
|
|
|
setPassedResult(PassEntityBy::AddressAndLength,
|
|
|
|
|
getResultEntity(interface.side().getCallDescription()));
|
|
|
|
|
mlir::Type lenTy = mlir::IndexType::get(&mlirContext);
|
|
|
|
|
std::optional<std::int64_t> constantLen = type.knownLength();
|
|
|
|
|
fir::CharacterType::LenType len =
|
|
|
|
|
constantLen ? *constantLen : fir::CharacterType::unknownLen();
|
|
|
|
|
mlir::Type charRefTy = fir::ReferenceType::get(
|
|
|
|
|
fir::CharacterType::get(&mlirContext, type.kind(), len));
|
|
|
|
|
mlir::Type boxCharTy = fir::BoxCharType::get(&mlirContext, type.kind());
|
|
|
|
|
addFirOperand(charRefTy, resultPosition, Property::CharAddress);
|
|
|
|
|
addFirOperand(lenTy, resultPosition, Property::CharLength);
|
|
|
|
|
/// For now, also return it by boxchar
|
|
|
|
|
addFirResult(boxCharTy, resultPosition, Property::BoxChar);
|
|
|
|
|
}
|
|
|
|
|
|
2022-02-16 20:27:23 +01:00
|
|
|
/// Return a vector with an attribute with the name of the argument if this
|
|
|
|
|
/// is a callee interface and the name is available. Otherwise, just return
|
|
|
|
|
/// an empty vector.
|
|
|
|
|
llvm::SmallVector<mlir::NamedAttribute>
|
|
|
|
|
dummyNameAttr(const FortranEntity &entity) {
|
|
|
|
|
if constexpr (std::is_same_v<FortranEntity,
|
|
|
|
|
std::optional<Fortran::common::Reference<
|
|
|
|
|
const Fortran::semantics::Symbol>>>) {
|
|
|
|
|
if (entity.has_value()) {
|
|
|
|
|
const Fortran::semantics::Symbol *argument = &*entity.value();
|
|
|
|
|
// "fir.bindc_name" is used for arguments for the sake of consistency
|
|
|
|
|
// with other attributes carrying surface syntax names in FIR.
|
|
|
|
|
return {mlir::NamedAttribute(
|
|
|
|
|
mlir::StringAttr::get(&mlirContext, "fir.bindc_name"),
|
|
|
|
|
mlir::StringAttr::get(&mlirContext,
|
|
|
|
|
toStringRef(argument->name())))};
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return {};
|
|
|
|
|
}
|
|
|
|
|
|
2022-03-07 19:55:48 +01:00
|
|
|
void handleImplicitDummy(
|
|
|
|
|
const DummyCharacteristics *characteristics,
|
|
|
|
|
const Fortran::evaluate::characteristics::DummyDataObject &obj,
|
|
|
|
|
const FortranEntity &entity) {
|
|
|
|
|
Fortran::evaluate::DynamicType dynamicType = obj.type.type();
|
|
|
|
|
if (dynamicType.category() == Fortran::common::TypeCategory::Character) {
|
|
|
|
|
mlir::Type boxCharTy =
|
|
|
|
|
fir::BoxCharType::get(&mlirContext, dynamicType.kind());
|
|
|
|
|
addFirOperand(boxCharTy, nextPassedArgPosition(), Property::BoxChar,
|
|
|
|
|
dummyNameAttr(entity));
|
|
|
|
|
addPassedArg(PassEntityBy::BoxChar, entity, characteristics);
|
|
|
|
|
} else {
|
|
|
|
|
// non-PDT derived type allowed in implicit interface.
|
|
|
|
|
mlir::Type type = translateDynamicType(dynamicType);
|
|
|
|
|
fir::SequenceType::Shape bounds = getBounds(obj.type.shape());
|
|
|
|
|
if (!bounds.empty())
|
|
|
|
|
type = fir::SequenceType::get(bounds, type);
|
|
|
|
|
mlir::Type refType = fir::ReferenceType::get(type);
|
|
|
|
|
addFirOperand(refType, nextPassedArgPosition(), Property::BaseAddress,
|
|
|
|
|
dummyNameAttr(entity));
|
|
|
|
|
addPassedArg(PassEntityBy::BaseAddress, entity, characteristics);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-02-24 17:16:02 +01:00
|
|
|
// Define when an explicit argument must be passed in a fir.box.
|
|
|
|
|
bool dummyRequiresBox(
|
|
|
|
|
const Fortran::evaluate::characteristics::DummyDataObject &obj) {
|
|
|
|
|
using ShapeAttr = Fortran::evaluate::characteristics::TypeAndShape::Attr;
|
|
|
|
|
using ShapeAttrs = Fortran::evaluate::characteristics::TypeAndShape::Attrs;
|
|
|
|
|
constexpr ShapeAttrs shapeRequiringBox = {
|
|
|
|
|
ShapeAttr::AssumedShape, ShapeAttr::DeferredShape,
|
|
|
|
|
ShapeAttr::AssumedRank, ShapeAttr::Coarray};
|
|
|
|
|
if ((obj.type.attrs() & shapeRequiringBox).any())
|
|
|
|
|
// Need to pass shape/coshape info in fir.box.
|
|
|
|
|
return true;
|
2023-04-14 08:43:33 +02:00
|
|
|
if (obj.type.type().IsPolymorphic() && !obj.type.type().IsAssumedType())
|
2022-02-24 17:16:02 +01:00
|
|
|
// Need to pass dynamic type info in fir.box.
|
|
|
|
|
return true;
|
|
|
|
|
if (const Fortran::semantics::DerivedTypeSpec *derived =
|
|
|
|
|
Fortran::evaluate::GetDerivedTypeSpec(obj.type.type()))
|
2022-04-20 09:57:41 +02:00
|
|
|
if (const Fortran::semantics::Scope *scope = derived->scope())
|
|
|
|
|
// Need to pass length type parameters in fir.box if any.
|
|
|
|
|
return scope->IsDerivedTypeWithLengthParameter();
|
2022-02-24 17:16:02 +01:00
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
mlir::Type
|
|
|
|
|
translateDynamicType(const Fortran::evaluate::DynamicType &dynamicType) {
|
|
|
|
|
Fortran::common::TypeCategory cat = dynamicType.category();
|
|
|
|
|
// DERIVED
|
|
|
|
|
if (cat == Fortran::common::TypeCategory::Derived) {
|
2022-10-05 23:04:59 +02:00
|
|
|
// TODO is kept under experimental flag until feature is complete.
|
|
|
|
|
if (dynamicType.IsPolymorphic() &&
|
2022-11-01 15:12:43 -07:00
|
|
|
!getConverter().getLoweringOptions().getPolymorphicTypeImpl())
|
2022-10-05 23:04:59 +02:00
|
|
|
TODO(interface.converter.getCurrentLocation(),
|
|
|
|
|
"support for polymorphic types");
|
|
|
|
|
|
2022-10-04 09:42:39 +02:00
|
|
|
if (dynamicType.IsUnlimitedPolymorphic())
|
|
|
|
|
return mlir::NoneType::get(&mlirContext);
|
2022-03-10 18:06:20 +01:00
|
|
|
return getConverter().genType(dynamicType.GetDerivedTypeSpec());
|
2022-02-24 17:16:02 +01:00
|
|
|
}
|
|
|
|
|
// CHARACTER with compile time constant length.
|
|
|
|
|
if (cat == Fortran::common::TypeCategory::Character)
|
2022-03-01 22:57:58 +01:00
|
|
|
if (std::optional<std::int64_t> constantLen =
|
|
|
|
|
toInt64(dynamicType.GetCharLength()))
|
|
|
|
|
return getConverter().genType(cat, dynamicType.kind(), {*constantLen});
|
2022-02-24 17:16:02 +01:00
|
|
|
// INTEGER, REAL, LOGICAL, COMPLEX, and CHARACTER with dynamic length.
|
|
|
|
|
return getConverter().genType(cat, dynamicType.kind());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void handleExplicitDummy(
|
|
|
|
|
const DummyCharacteristics *characteristics,
|
|
|
|
|
const Fortran::evaluate::characteristics::DummyDataObject &obj,
|
|
|
|
|
const FortranEntity &entity, bool isBindC) {
|
|
|
|
|
using Attrs = Fortran::evaluate::characteristics::DummyDataObject::Attr;
|
|
|
|
|
|
|
|
|
|
bool isValueAttr = false;
|
|
|
|
|
[[maybe_unused]] mlir::Location loc =
|
|
|
|
|
interface.converter.getCurrentLocation();
|
|
|
|
|
llvm::SmallVector<mlir::NamedAttribute> attrs = dummyNameAttr(entity);
|
|
|
|
|
auto addMLIRAttr = [&](llvm::StringRef attr) {
|
|
|
|
|
attrs.emplace_back(mlir::StringAttr::get(&mlirContext, attr),
|
|
|
|
|
mlir::UnitAttr::get(&mlirContext));
|
|
|
|
|
};
|
|
|
|
|
if (obj.attrs.test(Attrs::Optional))
|
|
|
|
|
addMLIRAttr(fir::getOptionalAttrName());
|
|
|
|
|
if (obj.attrs.test(Attrs::Asynchronous))
|
2022-06-20 15:43:44 +02:00
|
|
|
TODO(loc, "ASYNCHRONOUS in procedure interface");
|
2022-02-24 17:16:02 +01:00
|
|
|
if (obj.attrs.test(Attrs::Contiguous))
|
|
|
|
|
addMLIRAttr(fir::getContiguousAttrName());
|
|
|
|
|
if (obj.attrs.test(Attrs::Value))
|
|
|
|
|
isValueAttr = true; // TODO: do we want an mlir::Attribute as well?
|
|
|
|
|
if (obj.attrs.test(Attrs::Volatile))
|
2022-06-20 15:43:44 +02:00
|
|
|
TODO(loc, "VOLATILE in procedure interface");
|
2022-02-24 17:16:02 +01:00
|
|
|
if (obj.attrs.test(Attrs::Target))
|
|
|
|
|
addMLIRAttr(fir::getTargetAttrName());
|
|
|
|
|
|
|
|
|
|
// TODO: intents that require special care (e.g finalization)
|
|
|
|
|
|
|
|
|
|
using ShapeAttr = Fortran::evaluate::characteristics::TypeAndShape::Attr;
|
|
|
|
|
const Fortran::evaluate::characteristics::TypeAndShape::Attrs &shapeAttrs =
|
|
|
|
|
obj.type.attrs();
|
|
|
|
|
if (shapeAttrs.test(ShapeAttr::AssumedRank))
|
2022-06-20 15:43:44 +02:00
|
|
|
TODO(loc, "assumed rank in procedure interface");
|
2022-02-24 17:16:02 +01:00
|
|
|
if (shapeAttrs.test(ShapeAttr::Coarray))
|
2022-06-20 15:43:44 +02:00
|
|
|
TODO(loc, "coarray in procedure interface");
|
2022-02-24 17:16:02 +01:00
|
|
|
|
|
|
|
|
// So far assume that if the argument cannot be passed by implicit interface
|
|
|
|
|
// it must be by box. That may no be always true (e.g for simple optionals)
|
|
|
|
|
|
|
|
|
|
Fortran::evaluate::DynamicType dynamicType = obj.type.type();
|
|
|
|
|
mlir::Type type = translateDynamicType(dynamicType);
|
|
|
|
|
fir::SequenceType::Shape bounds = getBounds(obj.type.shape());
|
|
|
|
|
if (!bounds.empty())
|
|
|
|
|
type = fir::SequenceType::get(bounds, type);
|
|
|
|
|
if (obj.attrs.test(Attrs::Allocatable))
|
|
|
|
|
type = fir::HeapType::get(type);
|
|
|
|
|
if (obj.attrs.test(Attrs::Pointer))
|
|
|
|
|
type = fir::PointerType::get(type);
|
2022-10-04 21:29:28 +02:00
|
|
|
mlir::Type boxType = fir::wrapInClassOrBoxType(
|
|
|
|
|
type, obj.type.type().IsPolymorphic(), obj.type.type().IsAssumedType());
|
2022-02-24 17:16:02 +01:00
|
|
|
|
2023-04-14 08:43:33 +02:00
|
|
|
if (obj.attrs.test(Attrs::Allocatable) || obj.attrs.test(Attrs::Pointer)) {
|
2022-10-04 09:42:39 +02:00
|
|
|
// Pass as fir.ref<fir.box> or fir.ref<fir.class>
|
2022-02-24 17:16:02 +01:00
|
|
|
mlir::Type boxRefType = fir::ReferenceType::get(boxType);
|
|
|
|
|
addFirOperand(boxRefType, nextPassedArgPosition(), Property::MutableBox,
|
|
|
|
|
attrs);
|
|
|
|
|
addPassedArg(PassEntityBy::MutableBox, entity, characteristics);
|
|
|
|
|
} else if (dummyRequiresBox(obj)) {
|
2022-10-04 09:42:39 +02:00
|
|
|
// Pass as fir.box or fir.class
|
2022-03-14 10:41:50 +01:00
|
|
|
if (isValueAttr)
|
|
|
|
|
TODO(loc, "assumed shape dummy argument with VALUE attribute");
|
2022-02-24 17:16:02 +01:00
|
|
|
addFirOperand(boxType, nextPassedArgPosition(), Property::Box, attrs);
|
|
|
|
|
addPassedArg(PassEntityBy::Box, entity, characteristics);
|
|
|
|
|
} else if (dynamicType.category() ==
|
|
|
|
|
Fortran::common::TypeCategory::Character) {
|
|
|
|
|
// Pass as fir.box_char
|
|
|
|
|
mlir::Type boxCharTy =
|
|
|
|
|
fir::BoxCharType::get(&mlirContext, dynamicType.kind());
|
|
|
|
|
addFirOperand(boxCharTy, nextPassedArgPosition(), Property::BoxChar,
|
|
|
|
|
attrs);
|
|
|
|
|
addPassedArg(isValueAttr ? PassEntityBy::CharBoxValueAttribute
|
|
|
|
|
: PassEntityBy::BoxChar,
|
|
|
|
|
entity, characteristics);
|
|
|
|
|
} else {
|
2022-10-22 09:15:02 +08:00
|
|
|
// Pass as fir.ref unless it's by VALUE and BIND(C). Also pass-by-value
|
|
|
|
|
// for numerical/logical scalar without OPTIONAL so that the behavior is
|
|
|
|
|
// consistent with gfortran/nvfortran.
|
|
|
|
|
// TODO: pass-by-value for derived type is not supported yet
|
2022-02-24 17:16:02 +01:00
|
|
|
mlir::Type passType = fir::ReferenceType::get(type);
|
|
|
|
|
PassEntityBy passBy = PassEntityBy::BaseAddress;
|
|
|
|
|
Property prop = Property::BaseAddress;
|
|
|
|
|
if (isValueAttr) {
|
2022-11-04 22:19:38 +08:00
|
|
|
bool isBuiltinCptrType = fir::isa_builtin_cptr_type(type);
|
2022-10-22 09:15:02 +08:00
|
|
|
if (isBindC || (!type.isa<fir::SequenceType>() &&
|
|
|
|
|
!obj.attrs.test(Attrs::Optional) &&
|
2022-11-04 22:19:38 +08:00
|
|
|
(dynamicType.category() !=
|
|
|
|
|
Fortran::common::TypeCategory::Derived ||
|
|
|
|
|
isBuiltinCptrType))) {
|
2022-02-24 17:16:02 +01:00
|
|
|
passBy = PassEntityBy::Value;
|
|
|
|
|
prop = Property::Value;
|
2022-11-04 22:19:38 +08:00
|
|
|
if (isBuiltinCptrType) {
|
2022-08-29 22:29:34 +08:00
|
|
|
auto recTy = type.dyn_cast<fir::RecordType>();
|
|
|
|
|
mlir::Type fieldTy = recTy.getTypeList()[0].second;
|
|
|
|
|
passType = fir::ReferenceType::get(fieldTy);
|
|
|
|
|
} else {
|
|
|
|
|
passType = type;
|
|
|
|
|
}
|
2022-02-24 17:16:02 +01:00
|
|
|
} else {
|
|
|
|
|
passBy = PassEntityBy::BaseAddressValueAttribute;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
addFirOperand(passType, nextPassedArgPosition(), prop, attrs);
|
|
|
|
|
addPassedArg(passBy, entity, characteristics);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-02-16 20:27:23 +01:00
|
|
|
void handleImplicitDummy(
|
|
|
|
|
const DummyCharacteristics *characteristics,
|
2022-03-07 19:55:48 +01:00
|
|
|
const Fortran::evaluate::characteristics::DummyProcedure &proc,
|
2022-02-16 20:27:23 +01:00
|
|
|
const FortranEntity &entity) {
|
2022-03-07 19:55:48 +01:00
|
|
|
if (proc.attrs.test(
|
|
|
|
|
Fortran::evaluate::characteristics::DummyProcedure::Attr::Pointer))
|
|
|
|
|
TODO(interface.converter.getCurrentLocation(),
|
|
|
|
|
"procedure pointer arguments");
|
|
|
|
|
// Otherwise, it is a dummy procedure.
|
|
|
|
|
const Fortran::evaluate::characteristics::Procedure &procedure =
|
|
|
|
|
proc.procedure.value();
|
|
|
|
|
mlir::Type funcType =
|
|
|
|
|
getProcedureDesignatorType(&procedure, interface.converter);
|
2023-01-07 22:26:48 -08:00
|
|
|
std::optional<Fortran::evaluate::DynamicType> resultTy =
|
2022-03-07 19:55:48 +01:00
|
|
|
getResultDynamicType(procedure);
|
|
|
|
|
if (resultTy && mustPassLengthWithDummyProcedure(procedure)) {
|
|
|
|
|
// The result length of dummy procedures that are character functions must
|
|
|
|
|
// be passed so that the dummy procedure can be called if it has assumed
|
|
|
|
|
// length on the callee side.
|
|
|
|
|
mlir::Type tupleType =
|
|
|
|
|
fir::factory::getCharacterProcedureTupleType(funcType);
|
|
|
|
|
llvm::StringRef charProcAttr = fir::getCharacterProcedureDummyAttrName();
|
|
|
|
|
addFirOperand(tupleType, nextPassedArgPosition(), Property::CharProcTuple,
|
|
|
|
|
{mlir::NamedAttribute{
|
|
|
|
|
mlir::StringAttr::get(&mlirContext, charProcAttr),
|
|
|
|
|
mlir::UnitAttr::get(&mlirContext)}});
|
|
|
|
|
addPassedArg(PassEntityBy::CharProcTuple, entity, characteristics);
|
|
|
|
|
return;
|
2022-02-16 20:27:23 +01:00
|
|
|
}
|
2022-03-07 19:55:48 +01:00
|
|
|
addFirOperand(funcType, nextPassedArgPosition(), Property::BaseAddress);
|
|
|
|
|
addPassedArg(PassEntityBy::BaseAddress, entity, characteristics);
|
2022-02-16 20:27:23 +01:00
|
|
|
}
|
|
|
|
|
|
2022-03-07 19:55:48 +01:00
|
|
|
void handleExplicitResult(
|
|
|
|
|
const Fortran::evaluate::characteristics::FunctionResult &result) {
|
|
|
|
|
using Attr = Fortran::evaluate::characteristics::FunctionResult::Attr;
|
|
|
|
|
|
|
|
|
|
if (result.IsProcedurePointer())
|
|
|
|
|
TODO(interface.converter.getCurrentLocation(),
|
|
|
|
|
"procedure pointer results");
|
|
|
|
|
const Fortran::evaluate::characteristics::TypeAndShape *typeAndShape =
|
|
|
|
|
result.GetTypeAndShape();
|
|
|
|
|
assert(typeAndShape && "expect type for non proc pointer result");
|
|
|
|
|
mlir::Type mlirType = translateDynamicType(typeAndShape->type());
|
|
|
|
|
fir::SequenceType::Shape bounds = getBounds(typeAndShape->shape());
|
2022-10-04 09:42:39 +02:00
|
|
|
const auto *resTypeAndShape{result.GetTypeAndShape()};
|
|
|
|
|
bool resIsPolymorphic =
|
|
|
|
|
resTypeAndShape && resTypeAndShape->type().IsPolymorphic();
|
2022-10-04 21:29:28 +02:00
|
|
|
bool resIsAssumedType =
|
|
|
|
|
resTypeAndShape && resTypeAndShape->type().IsAssumedType();
|
2022-03-07 19:55:48 +01:00
|
|
|
if (!bounds.empty())
|
|
|
|
|
mlirType = fir::SequenceType::get(bounds, mlirType);
|
|
|
|
|
if (result.attrs.test(Attr::Allocatable))
|
2022-10-04 09:42:39 +02:00
|
|
|
mlirType = fir::wrapInClassOrBoxType(fir::HeapType::get(mlirType),
|
2022-10-04 21:29:28 +02:00
|
|
|
resIsPolymorphic, resIsAssumedType);
|
2022-03-07 19:55:48 +01:00
|
|
|
if (result.attrs.test(Attr::Pointer))
|
2022-10-04 09:42:39 +02:00
|
|
|
mlirType = fir::wrapInClassOrBoxType(fir::PointerType::get(mlirType),
|
2022-10-04 21:29:28 +02:00
|
|
|
resIsPolymorphic, resIsAssumedType);
|
2022-03-07 19:55:48 +01:00
|
|
|
|
|
|
|
|
if (fir::isa_char(mlirType)) {
|
|
|
|
|
// Character scalar results must be passed as arguments in lowering so
|
|
|
|
|
// that an assumed length character function callee can access the result
|
|
|
|
|
// length. A function with a result requiring an explicit interface does
|
|
|
|
|
// not have to be compatible with assumed length function, but most
|
|
|
|
|
// compilers supports it.
|
|
|
|
|
handleImplicitCharacterResult(typeAndShape->type());
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
addFirResult(mlirType, FirPlaceHolder::resultEntityPosition,
|
|
|
|
|
Property::Value);
|
|
|
|
|
// Explicit results require the caller to allocate the storage and save the
|
|
|
|
|
// function result in the storage with a fir.save_result.
|
|
|
|
|
setSaveResult();
|
2022-02-16 20:27:23 +01:00
|
|
|
}
|
|
|
|
|
|
2022-03-07 19:55:48 +01:00
|
|
|
fir::SequenceType::Shape getBounds(const Fortran::evaluate::Shape &shape) {
|
|
|
|
|
fir::SequenceType::Shape bounds;
|
|
|
|
|
for (const std::optional<Fortran::evaluate::ExtentExpr> &extent : shape) {
|
|
|
|
|
fir::SequenceType::Extent bound = fir::SequenceType::getUnknownExtent();
|
|
|
|
|
if (std::optional<std::int64_t> i = toInt64(extent))
|
|
|
|
|
bound = *i;
|
|
|
|
|
bounds.emplace_back(bound);
|
|
|
|
|
}
|
|
|
|
|
return bounds;
|
|
|
|
|
}
|
|
|
|
|
std::optional<std::int64_t>
|
|
|
|
|
toInt64(std::optional<
|
|
|
|
|
Fortran::evaluate::Expr<Fortran::evaluate::SubscriptInteger>>
|
|
|
|
|
expr) {
|
|
|
|
|
if (expr)
|
|
|
|
|
return Fortran::evaluate::ToInt64(Fortran::evaluate::Fold(
|
|
|
|
|
getConverter().getFoldingContext(), toEvExpr(*expr)));
|
|
|
|
|
return std::nullopt;
|
|
|
|
|
}
|
2022-12-03 12:14:21 -08:00
|
|
|
void addFirOperand(
|
|
|
|
|
mlir::Type type, int entityPosition, Property p,
|
|
|
|
|
llvm::ArrayRef<mlir::NamedAttribute> attributes = std::nullopt) {
|
2022-02-16 20:27:23 +01:00
|
|
|
interface.inputs.emplace_back(
|
|
|
|
|
FirPlaceHolder{type, entityPosition, p, attributes});
|
|
|
|
|
}
|
|
|
|
|
void
|
|
|
|
|
addFirResult(mlir::Type type, int entityPosition, Property p,
|
2022-12-03 12:14:21 -08:00
|
|
|
llvm::ArrayRef<mlir::NamedAttribute> attributes = std::nullopt) {
|
2022-02-16 20:27:23 +01:00
|
|
|
interface.outputs.emplace_back(
|
|
|
|
|
FirPlaceHolder{type, entityPosition, p, attributes});
|
|
|
|
|
}
|
|
|
|
|
void addPassedArg(PassEntityBy p, FortranEntity entity,
|
|
|
|
|
const DummyCharacteristics *characteristics) {
|
|
|
|
|
interface.passedArguments.emplace_back(
|
2022-03-07 19:55:48 +01:00
|
|
|
PassedEntity{p, entity, emptyValue(), emptyValue(), characteristics});
|
2022-02-14 21:31:46 +01:00
|
|
|
}
|
2022-02-25 18:21:44 +01:00
|
|
|
void setPassedResult(PassEntityBy p, FortranEntity entity) {
|
|
|
|
|
interface.passedResult =
|
|
|
|
|
PassedEntity{p, entity, emptyValue(), emptyValue()};
|
|
|
|
|
}
|
|
|
|
|
void setSaveResult() { interface.saveResult = true; }
|
2022-02-16 20:27:23 +01:00
|
|
|
int nextPassedArgPosition() { return interface.passedArguments.size(); }
|
2022-02-14 21:31:46 +01:00
|
|
|
|
2022-02-25 18:21:44 +01:00
|
|
|
static FirValue emptyValue() {
|
|
|
|
|
if constexpr (std::is_same_v<Fortran::lower::CalleeInterface, T>) {
|
|
|
|
|
return {};
|
|
|
|
|
} else {
|
|
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-02-14 21:31:46 +01:00
|
|
|
Fortran::lower::AbstractConverter &getConverter() {
|
|
|
|
|
return interface.converter;
|
|
|
|
|
}
|
|
|
|
|
CallInterface &interface;
|
|
|
|
|
mlir::MLIRContext &mlirContext;
|
|
|
|
|
};
|
|
|
|
|
|
2022-02-23 19:48:07 +01:00
|
|
|
template <typename T>
|
|
|
|
|
bool Fortran::lower::CallInterface<T>::PassedEntity::isOptional() const {
|
|
|
|
|
if (!characteristics)
|
|
|
|
|
return false;
|
|
|
|
|
return characteristics->IsOptional();
|
|
|
|
|
}
|
|
|
|
|
template <typename T>
|
|
|
|
|
bool Fortran::lower::CallInterface<T>::PassedEntity::mayBeModifiedByCall()
|
|
|
|
|
const {
|
|
|
|
|
if (!characteristics)
|
|
|
|
|
return true;
|
2022-12-16 12:59:12 +01:00
|
|
|
if (characteristics->GetIntent() == Fortran::common::Intent::In)
|
|
|
|
|
return false;
|
2023-02-01 11:43:22 +01:00
|
|
|
return !hasValueAttribute();
|
2022-02-23 19:48:07 +01:00
|
|
|
}
|
|
|
|
|
template <typename T>
|
|
|
|
|
bool Fortran::lower::CallInterface<T>::PassedEntity::mayBeReadByCall() const {
|
|
|
|
|
if (!characteristics)
|
|
|
|
|
return true;
|
|
|
|
|
return characteristics->GetIntent() != Fortran::common::Intent::Out;
|
|
|
|
|
}
|
2022-09-08 10:15:36 +02:00
|
|
|
template <typename T>
|
|
|
|
|
bool Fortran::lower::CallInterface<T>::PassedEntity::isIntentOut() const {
|
|
|
|
|
if (!characteristics)
|
|
|
|
|
return true;
|
|
|
|
|
return characteristics->GetIntent() == Fortran::common::Intent::Out;
|
|
|
|
|
}
|
2022-09-26 15:09:30 +02:00
|
|
|
template <typename T>
|
|
|
|
|
bool Fortran::lower::CallInterface<T>::PassedEntity::mustBeMadeContiguous()
|
|
|
|
|
const {
|
|
|
|
|
if (!characteristics)
|
|
|
|
|
return true;
|
|
|
|
|
const auto *dummy =
|
|
|
|
|
std::get_if<Fortran::evaluate::characteristics::DummyDataObject>(
|
|
|
|
|
&characteristics->u);
|
|
|
|
|
if (!dummy)
|
|
|
|
|
return false;
|
|
|
|
|
const auto &shapeAttrs = dummy->type.attrs();
|
|
|
|
|
using ShapeAttrs = Fortran::evaluate::characteristics::TypeAndShape::Attr;
|
|
|
|
|
if (shapeAttrs.test(ShapeAttrs::AssumedRank) ||
|
|
|
|
|
shapeAttrs.test(ShapeAttrs::AssumedShape))
|
|
|
|
|
return dummy->attrs.test(
|
|
|
|
|
Fortran::evaluate::characteristics::DummyDataObject::Attr::Contiguous);
|
|
|
|
|
if (shapeAttrs.test(ShapeAttrs::DeferredShape))
|
|
|
|
|
return false;
|
|
|
|
|
// Explicit shape arrays are contiguous.
|
|
|
|
|
return dummy->type.Rank() > 0;
|
|
|
|
|
}
|
2022-02-23 19:48:07 +01:00
|
|
|
|
2023-02-01 11:43:22 +01:00
|
|
|
template <typename T>
|
|
|
|
|
bool Fortran::lower::CallInterface<T>::PassedEntity::hasValueAttribute() const {
|
|
|
|
|
if (!characteristics)
|
|
|
|
|
return false;
|
|
|
|
|
const auto *dummy =
|
|
|
|
|
std::get_if<Fortran::evaluate::characteristics::DummyDataObject>(
|
|
|
|
|
&characteristics->u);
|
|
|
|
|
return dummy &&
|
|
|
|
|
dummy->attrs.test(
|
|
|
|
|
Fortran::evaluate::characteristics::DummyDataObject::Attr::Value);
|
|
|
|
|
}
|
|
|
|
|
|
2022-02-14 21:31:46 +01:00
|
|
|
template <typename T>
|
|
|
|
|
void Fortran::lower::CallInterface<T>::determineInterface(
|
|
|
|
|
bool isImplicit,
|
|
|
|
|
const Fortran::evaluate::characteristics::Procedure &procedure) {
|
|
|
|
|
CallInterfaceImpl<T> impl(*this);
|
|
|
|
|
if (isImplicit)
|
|
|
|
|
impl.buildImplicitInterface(procedure);
|
|
|
|
|
else
|
2022-02-15 16:00:28 +01:00
|
|
|
impl.buildExplicitInterface(procedure);
|
2022-03-07 19:55:48 +01:00
|
|
|
// We only expect the extra host asspciations argument from the callee side as
|
|
|
|
|
// the definition of internal procedures will be present, and we'll always
|
|
|
|
|
// have a FuncOp definition in the ModuleOp, when lowering.
|
|
|
|
|
if constexpr (std::is_same_v<T, Fortran::lower::CalleeInterface>) {
|
|
|
|
|
if (side().hasHostAssociated())
|
|
|
|
|
impl.appendHostAssocTupleArg(side().getHostAssociatedTy());
|
|
|
|
|
}
|
2022-02-14 21:31:46 +01:00
|
|
|
}
|
|
|
|
|
|
2022-01-28 22:39:44 +01:00
|
|
|
template <typename T>
|
|
|
|
|
mlir::FunctionType Fortran::lower::CallInterface<T>::genFunctionType() {
|
2022-02-14 21:31:46 +01:00
|
|
|
llvm::SmallVector<mlir::Type> returnTys;
|
2022-02-16 20:27:23 +01:00
|
|
|
llvm::SmallVector<mlir::Type> inputTys;
|
2022-02-14 21:31:46 +01:00
|
|
|
for (const FirPlaceHolder &placeHolder : outputs)
|
|
|
|
|
returnTys.emplace_back(placeHolder.type);
|
2022-02-16 20:27:23 +01:00
|
|
|
for (const FirPlaceHolder &placeHolder : inputs)
|
|
|
|
|
inputTys.emplace_back(placeHolder.type);
|
|
|
|
|
return mlir::FunctionType::get(&converter.getMLIRContext(), inputTys,
|
|
|
|
|
returnTys);
|
2022-01-28 22:39:44 +01:00
|
|
|
}
|
|
|
|
|
|
2022-03-07 19:55:48 +01:00
|
|
|
template <typename T>
|
|
|
|
|
llvm::SmallVector<mlir::Type>
|
|
|
|
|
Fortran::lower::CallInterface<T>::getResultType() const {
|
|
|
|
|
llvm::SmallVector<mlir::Type> types;
|
|
|
|
|
for (const FirPlaceHolder &out : outputs)
|
|
|
|
|
types.emplace_back(out.type);
|
|
|
|
|
return types;
|
|
|
|
|
}
|
|
|
|
|
|
2022-01-28 22:39:44 +01:00
|
|
|
template class Fortran::lower::CallInterface<Fortran::lower::CalleeInterface>;
|
2022-02-23 19:48:07 +01:00
|
|
|
template class Fortran::lower::CallInterface<Fortran::lower::CallerInterface>;
|
2022-03-07 19:55:48 +01:00
|
|
|
|
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
|
// Function Type Translation
|
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
|
|
|
|
|
|
/// Build signature from characteristics when there is no Fortran entity to
|
|
|
|
|
/// associate with the arguments (i.e, this is not a call site or a procedure
|
|
|
|
|
/// declaration. This is needed when dealing with function pointers/dummy
|
|
|
|
|
/// arguments.
|
|
|
|
|
|
|
|
|
|
class SignatureBuilder;
|
|
|
|
|
template <>
|
|
|
|
|
struct Fortran::lower::PassedEntityTypes<SignatureBuilder> {
|
|
|
|
|
using FortranEntity = FakeEntity;
|
|
|
|
|
using FirValue = int;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/// SignatureBuilder is a CRTP implementation of CallInterface intended to
|
|
|
|
|
/// help translating characteristics::Procedure to mlir::FunctionType using
|
|
|
|
|
/// the CallInterface translation.
|
|
|
|
|
class SignatureBuilder
|
|
|
|
|
: public Fortran::lower::CallInterface<SignatureBuilder> {
|
|
|
|
|
public:
|
|
|
|
|
SignatureBuilder(const Fortran::evaluate::characteristics::Procedure &p,
|
|
|
|
|
Fortran::lower::AbstractConverter &c, bool forceImplicit)
|
|
|
|
|
: CallInterface{c}, proc{p} {
|
|
|
|
|
bool isImplicit = forceImplicit || proc.CanBeCalledViaImplicitInterface();
|
|
|
|
|
determineInterface(isImplicit, proc);
|
|
|
|
|
}
|
|
|
|
|
/// Does the procedure characteristics being translated have alternate
|
|
|
|
|
/// returns ?
|
|
|
|
|
bool hasAlternateReturns() const {
|
|
|
|
|
for (const Fortran::evaluate::characteristics::DummyArgument &dummy :
|
|
|
|
|
proc.dummyArguments)
|
|
|
|
|
if (std::holds_alternative<
|
|
|
|
|
Fortran::evaluate::characteristics::AlternateReturn>(dummy.u))
|
|
|
|
|
return true;
|
|
|
|
|
return false;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/// This is only here to fulfill CRTP dependencies and should not be called.
|
|
|
|
|
std::string getMangledName() const {
|
|
|
|
|
llvm_unreachable("trying to get name from SignatureBuilder");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// This is only here to fulfill CRTP dependencies and should not be called.
|
|
|
|
|
mlir::Location getCalleeLocation() const {
|
|
|
|
|
llvm_unreachable("trying to get callee location from SignatureBuilder");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// This is only here to fulfill CRTP dependencies and should not be called.
|
|
|
|
|
const Fortran::semantics::Symbol *getProcedureSymbol() const {
|
|
|
|
|
llvm_unreachable("trying to get callee symbol from SignatureBuilder");
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
Fortran::evaluate::characteristics::Procedure characterize() const {
|
|
|
|
|
return proc;
|
|
|
|
|
}
|
|
|
|
|
/// SignatureBuilder cannot be used on main program.
|
|
|
|
|
static constexpr bool isMainProgram() { return false; }
|
|
|
|
|
|
|
|
|
|
/// Return the characteristics::Procedure that is being translated to
|
|
|
|
|
/// mlir::FunctionType.
|
|
|
|
|
const Fortran::evaluate::characteristics::Procedure &
|
|
|
|
|
getCallDescription() const {
|
|
|
|
|
return proc;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// This is not the description of an indirect call.
|
|
|
|
|
static constexpr bool isIndirectCall() { return false; }
|
|
|
|
|
|
|
|
|
|
/// Return the translated signature.
|
|
|
|
|
mlir::FunctionType getFunctionType() { return genFunctionType(); }
|
|
|
|
|
|
|
|
|
|
// Copy of base implementation.
|
|
|
|
|
static constexpr bool hasHostAssociated() { return false; }
|
|
|
|
|
mlir::Type getHostAssociatedTy() const {
|
|
|
|
|
llvm_unreachable("getting host associated type in SignatureBuilder");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private:
|
|
|
|
|
const Fortran::evaluate::characteristics::Procedure &proc;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
mlir::FunctionType Fortran::lower::translateSignature(
|
|
|
|
|
const Fortran::evaluate::ProcedureDesignator &proc,
|
|
|
|
|
Fortran::lower::AbstractConverter &converter) {
|
|
|
|
|
std::optional<Fortran::evaluate::characteristics::Procedure> characteristics =
|
|
|
|
|
Fortran::evaluate::characteristics::Procedure::Characterize(
|
|
|
|
|
proc, converter.getFoldingContext());
|
|
|
|
|
// Most unrestricted intrinsic characteristic has the Elemental attribute
|
|
|
|
|
// which triggers CanBeCalledViaImplicitInterface to return false. However,
|
|
|
|
|
// using implicit interface rules is just fine here.
|
|
|
|
|
bool forceImplicit = proc.GetSpecificIntrinsic();
|
|
|
|
|
return SignatureBuilder{characteristics.value(), converter, forceImplicit}
|
|
|
|
|
.getFunctionType();
|
|
|
|
|
}
|
|
|
|
|
|
2022-04-18 11:53:47 -07:00
|
|
|
mlir::func::FuncOp Fortran::lower::getOrDeclareFunction(
|
2022-03-07 19:55:48 +01:00
|
|
|
llvm::StringRef name, const Fortran::evaluate::ProcedureDesignator &proc,
|
|
|
|
|
Fortran::lower::AbstractConverter &converter) {
|
|
|
|
|
mlir::ModuleOp module = converter.getModuleOp();
|
2022-04-18 11:53:47 -07:00
|
|
|
mlir::func::FuncOp func = fir::FirOpBuilder::getNamedFunction(module, name);
|
2022-03-07 19:55:48 +01:00
|
|
|
if (func)
|
|
|
|
|
return func;
|
|
|
|
|
|
|
|
|
|
const Fortran::semantics::Symbol *symbol = proc.GetSymbol();
|
|
|
|
|
assert(symbol && "non user function in getOrDeclareFunction");
|
|
|
|
|
// getOrDeclareFunction is only used for functions not defined in the current
|
|
|
|
|
// program unit, so use the location of the procedure designator symbol, which
|
|
|
|
|
// is the first occurrence of the procedure in the program unit.
|
|
|
|
|
mlir::Location loc = converter.genLocation(symbol->name());
|
|
|
|
|
std::optional<Fortran::evaluate::characteristics::Procedure> characteristics =
|
|
|
|
|
Fortran::evaluate::characteristics::Procedure::Characterize(
|
|
|
|
|
proc, converter.getFoldingContext());
|
|
|
|
|
mlir::FunctionType ty = SignatureBuilder{characteristics.value(), converter,
|
|
|
|
|
/*forceImplicit=*/false}
|
|
|
|
|
.getFunctionType();
|
2022-04-18 11:53:47 -07:00
|
|
|
mlir::func::FuncOp newFunc =
|
2022-03-07 19:55:48 +01:00
|
|
|
fir::FirOpBuilder::createFunction(loc, module, name, ty);
|
|
|
|
|
addSymbolAttribute(newFunc, *symbol, converter.getMLIRContext());
|
|
|
|
|
return newFunc;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Is it required to pass a dummy procedure with \p characteristics as a tuple
|
|
|
|
|
// containing the function address and the result length ?
|
|
|
|
|
static bool mustPassLengthWithDummyProcedure(
|
|
|
|
|
const std::optional<Fortran::evaluate::characteristics::Procedure>
|
|
|
|
|
&characteristics) {
|
|
|
|
|
return characteristics &&
|
|
|
|
|
Fortran::lower::CallInterfaceImpl<SignatureBuilder>::
|
|
|
|
|
mustPassLengthWithDummyProcedure(*characteristics);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool Fortran::lower::mustPassLengthWithDummyProcedure(
|
|
|
|
|
const Fortran::evaluate::ProcedureDesignator &procedure,
|
|
|
|
|
Fortran::lower::AbstractConverter &converter) {
|
|
|
|
|
std::optional<Fortran::evaluate::characteristics::Procedure> characteristics =
|
|
|
|
|
Fortran::evaluate::characteristics::Procedure::Characterize(
|
|
|
|
|
procedure, converter.getFoldingContext());
|
|
|
|
|
return ::mustPassLengthWithDummyProcedure(characteristics);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
mlir::Type Fortran::lower::getDummyProcedureType(
|
|
|
|
|
const Fortran::semantics::Symbol &dummyProc,
|
|
|
|
|
Fortran::lower::AbstractConverter &converter) {
|
|
|
|
|
std::optional<Fortran::evaluate::characteristics::Procedure> iface =
|
|
|
|
|
Fortran::evaluate::characteristics::Procedure::Characterize(
|
|
|
|
|
dummyProc, converter.getFoldingContext());
|
|
|
|
|
mlir::Type procType = getProcedureDesignatorType(
|
|
|
|
|
iface.has_value() ? &*iface : nullptr, converter);
|
|
|
|
|
if (::mustPassLengthWithDummyProcedure(iface))
|
|
|
|
|
return fir::factory::getCharacterProcedureTupleType(procType);
|
|
|
|
|
return procType;
|
|
|
|
|
}
|
2022-08-29 22:29:34 +08:00
|
|
|
|
|
|
|
|
bool Fortran::lower::isCPtrArgByValueType(mlir::Type ty) {
|
|
|
|
|
return ty.isa<fir::ReferenceType>() &&
|
|
|
|
|
fir::isa_integer(fir::unwrapRefType(ty));
|
|
|
|
|
}
|