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.
267 lines
12 KiB
C++
267 lines
12 KiB
C++
//===-- Mangler.cpp -------------------------------------------------------===//
|
|
//
|
|
// 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/Mangler.h"
|
|
#include "flang/Common/reference.h"
|
|
#include "flang/Lower/Support/Utils.h"
|
|
#include "flang/Optimizer/Builder/Todo.h"
|
|
#include "flang/Optimizer/Dialect/FIRType.h"
|
|
#include "flang/Optimizer/Support/InternalNames.h"
|
|
#include "flang/Semantics/tools.h"
|
|
#include "llvm/ADT/ArrayRef.h"
|
|
#include "llvm/ADT/SmallVector.h"
|
|
#include "llvm/ADT/StringRef.h"
|
|
#include "llvm/Support/MD5.h"
|
|
|
|
/// Return all ancestor module and submodule scope names; all host procedure
|
|
/// and statement function scope names; and the innermost blockId containing
|
|
/// \p symbol.
|
|
static std::tuple<llvm::SmallVector<llvm::StringRef>,
|
|
llvm::SmallVector<llvm::StringRef>, std::int64_t>
|
|
ancestors(const Fortran::semantics::Symbol &symbol,
|
|
Fortran::lower::mangle::ScopeBlockIdMap &scopeBlockIdMap) {
|
|
llvm::SmallVector<const Fortran::semantics::Scope *> scopes;
|
|
for (auto *scp = &symbol.owner(); !scp->IsGlobal(); scp = &scp->parent())
|
|
scopes.push_back(scp);
|
|
llvm::SmallVector<llvm::StringRef> modules;
|
|
llvm::SmallVector<llvm::StringRef> procs;
|
|
std::int64_t blockId = 0;
|
|
for (auto iter = scopes.rbegin(), rend = scopes.rend(); iter != rend;
|
|
++iter) {
|
|
auto *scp = *iter;
|
|
switch (scp->kind()) {
|
|
case Fortran::semantics::Scope::Kind::Module:
|
|
modules.emplace_back(toStringRef(scp->symbol()->name()));
|
|
break;
|
|
case Fortran::semantics::Scope::Kind::Subprogram:
|
|
procs.emplace_back(toStringRef(scp->symbol()->name()));
|
|
break;
|
|
case Fortran::semantics::Scope::Kind::MainProgram:
|
|
// Do not use the main program name, if any, because it may collide
|
|
// with a procedure of the same name in another compilation unit.
|
|
// This is nonconformant, but universally allowed.
|
|
procs.emplace_back(llvm::StringRef(""));
|
|
break;
|
|
case Fortran::semantics::Scope::Kind::BlockConstruct: {
|
|
auto it = scopeBlockIdMap.find(scp);
|
|
assert(it != scopeBlockIdMap.end() && it->second &&
|
|
"invalid block identifier");
|
|
blockId = it->second;
|
|
} break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
return {modules, procs, blockId};
|
|
}
|
|
|
|
// Mangle the name of \p symbol to make it globally unique.
|
|
std::string
|
|
Fortran::lower::mangle::mangleName(const Fortran::semantics::Symbol &symbol,
|
|
ScopeBlockIdMap &scopeBlockIdMap,
|
|
bool keepExternalInScope) {
|
|
// Resolve module and host associations before mangling.
|
|
const auto &ultimateSymbol = symbol.GetUltimate();
|
|
|
|
// The Fortran and BIND(C) namespaces are counterintuitive. A BIND(C) name is
|
|
// substituted early, and has precedence over the Fortran name. This allows
|
|
// multiple procedures or objects with identical Fortran names to legally
|
|
// coexist. The BIND(C) name is unique.
|
|
if (auto *overrideName = ultimateSymbol.GetBindName())
|
|
return *overrideName;
|
|
|
|
// TODO: A procedure that inherits BIND(C) through another interface
|
|
// (procedure(iface)) should be dealt with in GetBindName() or some wrapper.
|
|
if (!Fortran::semantics::IsPointer(ultimateSymbol) &&
|
|
Fortran::semantics::IsBindCProcedure(ultimateSymbol) &&
|
|
Fortran::semantics::ClassifyProcedure(symbol) !=
|
|
Fortran::semantics::ProcedureDefinitionClass::Internal)
|
|
return ultimateSymbol.name().ToString();
|
|
|
|
llvm::StringRef symbolName = toStringRef(ultimateSymbol.name());
|
|
llvm::SmallVector<llvm::StringRef> modules;
|
|
llvm::SmallVector<llvm::StringRef> procs;
|
|
std::int64_t blockId;
|
|
|
|
// mangle ObjectEntityDetails or AssocEntityDetails symbols.
|
|
auto mangleObject = [&]() -> std::string {
|
|
std::tie(modules, procs, blockId) =
|
|
ancestors(ultimateSymbol, scopeBlockIdMap);
|
|
if (Fortran::semantics::IsNamedConstant(ultimateSymbol))
|
|
return fir::NameUniquer::doConstant(modules, procs, blockId, symbolName);
|
|
return fir::NameUniquer::doVariable(modules, procs, blockId, symbolName);
|
|
};
|
|
|
|
return std::visit(
|
|
Fortran::common::visitors{
|
|
[&](const Fortran::semantics::MainProgramDetails &) {
|
|
return fir::NameUniquer::doProgramEntry().str();
|
|
},
|
|
[&](const Fortran::semantics::SubprogramDetails &subpDetails) {
|
|
// Mangle external procedure without any scope prefix.
|
|
if (!keepExternalInScope &&
|
|
Fortran::semantics::IsExternal(ultimateSymbol))
|
|
return fir::NameUniquer::doProcedure(std::nullopt, std::nullopt,
|
|
symbolName);
|
|
// A separate module procedure must be mangled according to its
|
|
// declaration scope, not its definition scope.
|
|
const Fortran::semantics::Symbol *interface = &ultimateSymbol;
|
|
if (interface->attrs().test(Fortran::semantics::Attr::MODULE) &&
|
|
interface->owner().IsSubmodule() && !subpDetails.isInterface())
|
|
interface = subpDetails.moduleInterface();
|
|
assert(interface && "Separate module procedure must be declared");
|
|
std::tie(modules, procs, blockId) =
|
|
ancestors(*interface, scopeBlockIdMap);
|
|
return fir::NameUniquer::doProcedure(modules, procs, symbolName);
|
|
},
|
|
[&](const Fortran::semantics::ProcEntityDetails &) {
|
|
// Mangle procedure pointers and dummy procedures as variables.
|
|
if (Fortran::semantics::IsPointer(ultimateSymbol) ||
|
|
Fortran::semantics::IsDummy(ultimateSymbol)) {
|
|
std::tie(modules, procs, blockId) =
|
|
ancestors(ultimateSymbol, scopeBlockIdMap);
|
|
return fir::NameUniquer::doVariable(modules, procs, blockId,
|
|
symbolName);
|
|
}
|
|
// Otherwise, this is an external procedure, with or without an
|
|
// explicit EXTERNAL attribute. Mangle it without any prefix.
|
|
return fir::NameUniquer::doProcedure(std::nullopt, std::nullopt,
|
|
symbolName);
|
|
},
|
|
[&](const Fortran::semantics::ObjectEntityDetails &) {
|
|
return mangleObject();
|
|
},
|
|
[&](const Fortran::semantics::AssocEntityDetails &) {
|
|
return mangleObject();
|
|
},
|
|
[&](const Fortran::semantics::NamelistDetails &) {
|
|
std::tie(modules, procs, blockId) =
|
|
ancestors(ultimateSymbol, scopeBlockIdMap);
|
|
return fir::NameUniquer::doNamelistGroup(modules, procs,
|
|
symbolName);
|
|
},
|
|
[&](const Fortran::semantics::CommonBlockDetails &) {
|
|
return fir::NameUniquer::doCommonBlock(symbolName);
|
|
},
|
|
[&](const Fortran::semantics::ProcBindingDetails &procBinding) {
|
|
return mangleName(procBinding.symbol(), scopeBlockIdMap,
|
|
keepExternalInScope);
|
|
},
|
|
[&](const Fortran::semantics::DerivedTypeDetails &) -> std::string {
|
|
// Derived type mangling must use mangleName(DerivedTypeSpec) so
|
|
// that kind type parameter values can be mangled.
|
|
llvm::report_fatal_error(
|
|
"only derived type instances can be mangled");
|
|
},
|
|
[](const auto &) -> std::string { TODO_NOLOC("symbol mangling"); },
|
|
},
|
|
ultimateSymbol.details());
|
|
}
|
|
|
|
std::string
|
|
Fortran::lower::mangle::mangleName(const Fortran::semantics::Symbol &symbol,
|
|
bool keepExternalInScope) {
|
|
assert(symbol.owner().kind() !=
|
|
Fortran::semantics::Scope::Kind::BlockConstruct &&
|
|
"block object mangling must specify a scopeBlockIdMap");
|
|
ScopeBlockIdMap scopeBlockIdMap;
|
|
return mangleName(symbol, scopeBlockIdMap, keepExternalInScope);
|
|
}
|
|
|
|
std::string Fortran::lower::mangle::mangleName(
|
|
const Fortran::semantics::DerivedTypeSpec &derivedType,
|
|
ScopeBlockIdMap &scopeBlockIdMap) {
|
|
// Resolve module and host associations before mangling.
|
|
const Fortran::semantics::Symbol &ultimateSymbol =
|
|
derivedType.typeSymbol().GetUltimate();
|
|
|
|
llvm::StringRef symbolName = toStringRef(ultimateSymbol.name());
|
|
llvm::SmallVector<llvm::StringRef> modules;
|
|
llvm::SmallVector<llvm::StringRef> procs;
|
|
std::int64_t blockId;
|
|
std::tie(modules, procs, blockId) =
|
|
ancestors(ultimateSymbol, scopeBlockIdMap);
|
|
llvm::SmallVector<std::int64_t> kinds;
|
|
for (const auto ¶m :
|
|
Fortran::semantics::OrderParameterDeclarations(ultimateSymbol)) {
|
|
const auto ¶mDetails =
|
|
param->get<Fortran::semantics::TypeParamDetails>();
|
|
if (paramDetails.attr() == Fortran::common::TypeParamAttr::Kind) {
|
|
const Fortran::semantics::ParamValue *paramValue =
|
|
derivedType.FindParameter(param->name());
|
|
assert(paramValue && "derived type kind parameter value not found");
|
|
const Fortran::semantics::MaybeIntExpr paramExpr =
|
|
paramValue->GetExplicit();
|
|
assert(paramExpr && "derived type kind param not explicit");
|
|
std::optional<int64_t> init =
|
|
Fortran::evaluate::ToInt64(paramValue->GetExplicit());
|
|
assert(init && "derived type kind param is not constant");
|
|
kinds.emplace_back(*init);
|
|
}
|
|
}
|
|
return fir::NameUniquer::doType(modules, procs, blockId, symbolName, kinds);
|
|
}
|
|
|
|
std::string Fortran::lower::mangle::demangleName(llvm::StringRef name) {
|
|
auto result = fir::NameUniquer::deconstruct(name);
|
|
return result.second.name;
|
|
}
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
// Array Literals Mangling
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
static std::string typeToString(Fortran::common::TypeCategory cat, int kind,
|
|
llvm::StringRef derivedName) {
|
|
switch (cat) {
|
|
case Fortran::common::TypeCategory::Integer:
|
|
return "i" + std::to_string(kind);
|
|
case Fortran::common::TypeCategory::Real:
|
|
return "r" + std::to_string(kind);
|
|
case Fortran::common::TypeCategory::Complex:
|
|
return "z" + std::to_string(kind);
|
|
case Fortran::common::TypeCategory::Logical:
|
|
return "l" + std::to_string(kind);
|
|
case Fortran::common::TypeCategory::Character:
|
|
return "c" + std::to_string(kind);
|
|
case Fortran::common::TypeCategory::Derived:
|
|
return derivedName.str();
|
|
}
|
|
llvm_unreachable("bad TypeCategory");
|
|
}
|
|
|
|
std::string Fortran::lower::mangle::mangleArrayLiteral(
|
|
const uint8_t *addr, size_t size,
|
|
const Fortran::evaluate::ConstantSubscripts &shape,
|
|
Fortran::common::TypeCategory cat, int kind,
|
|
Fortran::common::ConstantSubscript charLen, llvm::StringRef derivedName) {
|
|
std::string typeId;
|
|
for (Fortran::evaluate::ConstantSubscript extent : shape)
|
|
typeId.append(std::to_string(extent)).append("x");
|
|
if (charLen >= 0)
|
|
typeId.append(std::to_string(charLen)).append("x");
|
|
typeId.append(typeToString(cat, kind, derivedName));
|
|
std::string name =
|
|
fir::NameUniquer::doGenerated("ro."s.append(typeId).append("."));
|
|
if (!size)
|
|
return name += "null";
|
|
llvm::MD5 hashValue{};
|
|
hashValue.update(llvm::ArrayRef<uint8_t>{addr, size});
|
|
llvm::MD5::MD5Result hashResult;
|
|
hashValue.final(hashResult);
|
|
llvm::SmallString<32> hashString;
|
|
llvm::MD5::stringifyResult(hashResult, hashString);
|
|
return name += hashString.c_str();
|
|
}
|
|
|
|
std::string Fortran::lower::mangle::globalNamelistDescriptorName(
|
|
const Fortran::semantics::Symbol &sym) {
|
|
std::string name = mangleName(sym);
|
|
return IsAllocatableOrPointer(sym) ? name : name + ".desc"s;
|
|
}
|