Skip to content

[flang][debug] Improve handling of cyclic derived types. #122770

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jan 20, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions flang/lib/Optimizer/Dialect/FIRType.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,7 @@ mlir::Type getDerivedType(mlir::Type ty) {
return seq.getEleTy();
return p.getEleTy();
})
.Case<fir::BoxType>([](auto p) { return getDerivedType(p.getEleTy()); })
.Default([](mlir::Type t) { return t; });
}

Expand Down
108 changes: 58 additions & 50 deletions flang/lib/Optimizer/Transforms/DebugTypeGenerator.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -273,55 +273,6 @@ static mlir::LLVM::DITypeAttr getUnderlyingType(mlir::LLVM::DITypeAttr Ty) {
return Ty;
}

// Currently, the handling of recursive debug type in mlir has some limitations.
// Those limitations were discussed at the end of the thread for following PR.
// https://github.com/llvm/llvm-project/pull/106571
//
// Problem could be explained with the following example code:
// type t2
// type(t1), pointer :: p1
// end type
// type t1
// type(t2), pointer :: p2
// end type
// In the description below, type_self means a temporary type that is generated
// as a place holder while the members of that type are being processed.
//
// If we process t1 first then we will have the following structure after it has
// been processed.
// t1 -> t2 -> t1_self
// This is because when we started processing t2, we did not have the complete
// t1 but its place holder t1_self.
// Now if some entity requires t2, we will already have that in cache and will
// return it. But this t2 refers to t1_self and not to t1. In mlir handling,
// only those types are allowed to have _self reference which are wrapped by
// entity whose reference it is. So t1 -> t2 -> t1_self is ok because the
// t1_self reference can be resolved by the outer t1. But standalone t2 is not
// because there will be no way to resolve it. Until this is fixed in mlir, we
// avoid caching such types. Please see DebugTranslation::translateRecursive for
// details on how mlir handles recursive types.
static bool canCacheThisType(mlir::LLVM::DICompositeTypeAttr comTy) {
for (auto el : comTy.getElements()) {
if (auto mem =
mlir::dyn_cast_if_present<mlir::LLVM::DIDerivedTypeAttr>(el)) {
mlir::LLVM::DITypeAttr memTy = getUnderlyingType(mem.getBaseType());
if (auto baseTy =
mlir::dyn_cast_if_present<mlir::LLVM::DICompositeTypeAttr>(
memTy)) {
// We will not cache a type if one of its member meets the following
// conditions:
// 1. It is a structure type
// 2. It is a place holder type (getIsRecSelf() is true)
// 3. It is not a self reference. It is ok to have t1_self in t1.
if (baseTy.getTag() == llvm::dwarf::DW_TAG_structure_type &&
baseTy.getIsRecSelf() && (comTy.getRecId() != baseTy.getRecId()))
return false;
}
}
}
return true;
}

std::pair<std::uint64_t, unsigned short>
DebugTypeGenerator::getFieldSizeAndAlign(mlir::Type fieldTy) {
mlir::Type llvmTy;
Expand All @@ -343,6 +294,7 @@ mlir::LLVM::DITypeAttr DebugTypeGenerator::convertRecordType(
if (iter != typeCache.end())
return iter->second;

bool canCacheThisType = true;
llvm::SmallVector<mlir::LLVM::DINodeAttr> elements;
mlir::MLIRContext *context = module.getContext();
auto recId = mlir::DistinctAttr::create(mlir::UnitAttr::get(context));
Expand Down Expand Up @@ -406,6 +358,62 @@ mlir::LLVM::DITypeAttr DebugTypeGenerator::convertRecordType(
/*extra data=*/nullptr);
elements.push_back(tyAttr);
offset += llvm::alignTo(byteSize, byteAlign);

// Currently, the handling of recursive debug type in mlir has some
// limitations that were discussed at the end of the thread for following
// PR.
// https://github.com/llvm/llvm-project/pull/106571
//
// Problem could be explained with the following example code:
// type t2
// type(t1), pointer :: p1
// end type
// type t1
// type(t2), pointer :: p2
// end type
// In the description below, type_self means a temporary type that is
// generated
// as a place holder while the members of that type are being processed.
//
// If we process t1 first then we will have the following structure after
// it has been processed.
// t1 -> t2 -> t1_self
// This is because when we started processing t2, we did not have the
// complete t1 but its place holder t1_self.
// Now if some entity requires t2, we will already have that in cache and
// will return it. But this t2 refers to t1_self and not to t1. In mlir
// handling, only those types are allowed to have _self reference which are
// wrapped by entity whose reference it is. So t1 -> t2 -> t1_self is ok
// because the t1_self reference can be resolved by the outer t1. But
// standalone t2 is not because there will be no way to resolve it. Until
// this is fixed in mlir, we avoid caching such types. Please see
// DebugTranslation::translateRecursive for details on how mlir handles
// recursive types.
// The code below checks for situation where it will be unsafe to cache
// a type to avoid this problem. We do that in 2 situations.
// 1. If a member is record type, then its type would have been processed
// before reaching here. If it is not in the cache, it means that it was
// found to be unsafe to cache. So any type containing it will also not
// be cached
// 2. The type of the member is found in the cache but it is a place holder.
// In this case, its recID should match the recID of the type we are
// processing. This helps us to cache the following type.
// type t
// type(t), allocatable :: p
// end type
mlir::Type baseTy = getDerivedType(fieldTy);
if (auto recTy = mlir::dyn_cast<fir::RecordType>(baseTy)) {
auto iter = typeCache.find(recTy);
if (iter == typeCache.end())
canCacheThisType = false;
else {
if (auto tyAttr =
mlir::dyn_cast<mlir::LLVM::DICompositeTypeAttr>(iter->second)) {
if (tyAttr.getIsRecSelf() && tyAttr.getRecId() != recId)
canCacheThisType = false;
}
}
}
}

auto finalAttr = mlir::LLVM::DICompositeTypeAttr::get(
Expand All @@ -414,7 +422,7 @@ mlir::LLVM::DITypeAttr DebugTypeGenerator::convertRecordType(
/*baseType=*/nullptr, mlir::LLVM::DIFlags::Zero, offset * 8,
/*alignInBits=*/0, elements, /*dataLocation=*/nullptr, /*rank=*/nullptr,
/*allocated=*/nullptr, /*associated=*/nullptr);
if (canCacheThisType(finalAttr)) {
if (canCacheThisType) {
typeCache[Ty] = finalAttr;
} else {
auto iter = typeCache.find(Ty);
Expand Down
32 changes: 32 additions & 0 deletions flang/test/Integration/debug-cyclic-derived-type-3.f90
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
! RUN: %flang_fc1 -emit-llvm -debug-info-kind=standalone %s -o -

! mainly test that this program does not cause an assertion failure
! testcase for issue 122024

module m1
type t1
type(t2),pointer :: x1
end type
type t2
type(t3),pointer :: x2
end type
type t3
type(t1),pointer :: x3
end type
end

program test
use m1
type(t1),pointer :: foo
allocate(foo)
allocate(foo%x1)
allocate(foo%x1%x2)
allocate(foo%x1%x2%x3)
call sub1(foo%x1)
print *,'done'
end program

subroutine sub1(bar)
use m1
type(t2) :: bar
end subroutine
Loading