Skip to content

[flang][debug] Support ClassType. #114809

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 3 commits into from
Nov 18, 2024
Merged

[flang][debug] Support ClassType. #114809

merged 3 commits into from
Nov 18, 2024

Conversation

abidh
Copy link
Contributor

@abidh abidh commented Nov 4, 2024

This PR adds the handling of ClassType. It is treated as pointer to the underlying type. Note that ClassType when passed to the function have double indirection so it is represented as pointer to type (compared to other types which may have a single indirection).

If ClassType wraps a pointer or allocatable then we take care to generate it as PTR -> type (and not PTR -> PTR -> type).

This is how it looks like in the debugger.

subroutine test_proc (this)
    class(test_type), intent (inout) :: this
    allocate (this%b (3, 2))
    call fill_array_2d (this%b)
    print *, this%a
end
(gdb) p this
$6 = (PTR TO -> ( Type test_type )) 0x2052a0
(gdb) p this%a
$7 = 0
(gdb) p this%b
$8 = ((1, 2, 3) (4, 5, 6))

@llvmbot llvmbot added flang Flang issues not falling into any other category flang:fir-hlfir labels Nov 4, 2024
@llvmbot
Copy link
Member

llvmbot commented Nov 4, 2024

@llvm/pr-subscribers-flang-fir-hlfir

Author: Abid Qadeer (abidh)

Changes

This PR adds the handling of ClassType. It is treated as pointer to the underlying type. Note that ClassType when passed to the function have double indirection so it is represented as pointer to type (compared to other types which may have a single indirection).

If ClassType wraps a pointer or allocatable then we take care to generate it as PTR -> type (and not PTR -> PTR -> type).

This is how it looks like in the debugger.

subroutine test_proc (this)
    class(test_type), intent (inout) :: this
    allocate (this%b (3, 2))
    call fill_array_2d (this%b)
    print *, this%a
end
(gdb) p this
$6 = (PTR TO -> ( Type test_type )) 0x2052a0
(gdb) p this%a
$7 = 0
(gdb) p this%b
$8 = ((1, 2, 3) (4, 5, 6))


Full diff: https://github.com/llvm/llvm-project/pull/114809.diff

2 Files Affected:

  • (modified) flang/lib/Optimizer/Transforms/DebugTypeGenerator.cpp (+14)
  • (added) flang/test/Transforms/debug-class-type.fir (+27)
diff --git a/flang/lib/Optimizer/Transforms/DebugTypeGenerator.cpp b/flang/lib/Optimizer/Transforms/DebugTypeGenerator.cpp
index a070c87137fa16..28c4b9fac74ec5 100644
--- a/flang/lib/Optimizer/Transforms/DebugTypeGenerator.cpp
+++ b/flang/lib/Optimizer/Transforms/DebugTypeGenerator.cpp
@@ -617,6 +617,20 @@ DebugTypeGenerator::convertType(mlir::Type Ty, mlir::LLVM::DIFileAttr fileAttr,
     return convertRecordType(recTy, fileAttr, scope, declOp);
   } else if (auto tupleTy = mlir::dyn_cast_if_present<mlir::TupleType>(Ty)) {
     return convertTupleType(tupleTy, fileAttr, scope, declOp);
+  } else if (auto classTy = mlir::dyn_cast_if_present<fir::ClassType>(Ty)) {
+    // ClassType when passed to the function have double indirection so it
+    // is represented as pointer to type (and not type as a RecordType will be
+    // for example). If ClassType wraps a pointer or allocatable then we get
+    // the real underlying type to avoid translating the Ty to
+    // Ptr -> Ptr -> type.
+    mlir::Type elTy = classTy.getEleTy();
+    if (auto ptrTy = mlir::dyn_cast_if_present<fir::PointerType>(elTy))
+      elTy = ptrTy.getElementType();
+    else if (auto heapTy = mlir::dyn_cast_if_present<fir::HeapType>(elTy))
+      elTy = heapTy.getElementType();
+    return convertPointerLikeType(elTy, fileAttr, scope, declOp,
+                                  /*genAllocated=*/false,
+                                  /*genAssociated=*/false);
   } else if (auto refTy = mlir::dyn_cast_if_present<fir::ReferenceType>(Ty)) {
     auto elTy = refTy.getEleTy();
     return convertPointerLikeType(elTy, fileAttr, scope, declOp,
diff --git a/flang/test/Transforms/debug-class-type.fir b/flang/test/Transforms/debug-class-type.fir
new file mode 100644
index 00000000000000..e11dc7b2a2086a
--- /dev/null
+++ b/flang/test/Transforms/debug-class-type.fir
@@ -0,0 +1,27 @@
+// RUN: fir-opt --add-debug-info --mlir-print-debuginfo %s | FileCheck %s
+
+module attributes {dlti.dl_spec = #dlti.dl_spec<>} {
+  fir.type_info @_QTtest_type nofinal : !fir.type<_QTtest_type{a:i32,b:!fir.box<!fir.heap<!fir.array<?x?xf32>>>}> dispatch_table {
+    fir.dt_entry "test_proc", @_QPtest_proc
+  } loc(#loc1)
+  func.func private @_QPtest_proc(%arg0: !fir.class<!fir.type<_QTtest_type{a:i32,b:!fir.box<!fir.heap<!fir.array<?x?xf32>>>}>>)
+
+    func.func @test()  {
+    %0 = fir.address_of(@_QFEx) : !fir.ref<!fir.class<!fir.heap<!fir.type<_QTtest_type{a:i32,b:!fir.box<!fir.heap<!fir.array<?x?xf32>>>}>>>>
+    %1 = fircg.ext_declare %0 {uniq_name = "_QFEx"} : (!fir.ref<!fir.class<!fir.heap<!fir.type<_QTtest_type{a:i32,b:!fir.box<!fir.heap<!fir.array<?x?xf32>>>}>>>>) -> !fir.ref<!fir.class<!fir.heap<!fir.type<_QTtest_type{a:i32,b:!fir.box<!fir.heap<!fir.array<?x?xf32>>>}>>>> loc(#loc3)
+    %2 = fir.address_of(@_QFEy) : !fir.ref<!fir.class<!fir.ptr<!fir.type<_QTtest_type{a:i32,b:!fir.box<!fir.heap<!fir.array<?x?xf32>>>}>>>>
+    %3 = fircg.ext_declare %2 {uniq_name = "_QFEy"} : (!fir.ref<!fir.class<!fir.ptr<!fir.type<_QTtest_type{a:i32,b:!fir.box<!fir.heap<!fir.array<?x?xf32>>>}>>>>) -> !fir.ref<!fir.class<!fir.ptr<!fir.type<_QTtest_type{a:i32,b:!fir.box<!fir.heap<!fir.array<?x?xf32>>>}>>>> loc(#loc4)
+    return
+  } loc(#loc2)
+}
+
+#loc1 = loc("./simple.f90":2:1)
+#loc2 = loc("./simple.f90":10:1)
+#loc3 = loc("./simple.f90":15:1)
+#loc4 = loc("./simple.f90":22:1)
+
+// CHECK-DAG: #[[TY1:.*]] = #llvm.di_composite_type<{{.*}}name = "test_type"{{.*}}>
+// CHECK-DAG: #[[TY2:.*]] = #llvm.di_derived_type<tag = DW_TAG_pointer_type, name = "", baseType = #[[TY1]]{{.*}}>
+// CHECK-DAG: #llvm.di_subroutine_type<callingConvention = DW_CC_normal, types = #di_null_type, #[[TY2]]>
+// CHECK-DAG: #llvm.di_local_variable<{{.*}}name = "x"{{.*}}type = #[[TY2]]>
+// CHECK-DAG: #llvm.di_local_variable<{{.*}}name = "y"{{.*}}type = #[[TY2]]>

Copy link
Contributor

@jeanPerier jeanPerier left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The fir.class type is used for polymorphic entities (including unlimited polymorphic entities, CLASS(*)).

Does DWARF provide support for querying the dynamic type at runtime?

I saw dwarf has a DW_TAG_dynamic_type tag, but that seems related to POINTER/ALLOCATABLE length parametrized derived types rather than polymorphism.

@@ -617,6 +617,20 @@ DebugTypeGenerator::convertType(mlir::Type Ty, mlir::LLVM::DIFileAttr fileAttr,
return convertRecordType(recTy, fileAttr, scope, declOp);
} else if (auto tupleTy = mlir::dyn_cast_if_present<mlir::TupleType>(Ty)) {
return convertTupleType(tupleTy, fileAttr, scope, declOp);
} else if (auto classTy = mlir::dyn_cast_if_present<fir::ClassType>(Ty)) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ClassType and BoxType are implemented in the same way in the runtime, the difference is that the dynamic type inside a ClassType may mismatch its static type.

You can actually merge the handling of ClassType and BoxType by replacing auto boxTy = mlir::dyn_cast_or_null<fir::BoxType>(Ty) with auto boxTy = mlir::dyn_cast_or_null<fir::BaseBoxType>(Ty).

You would then get the static type description of the entity in the DWARF (which seems to be what is being done here).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for your comments. I updated the patch to handle it with the BoxType. I also made another small change to handle cases like CLASS(*).

@abidh
Copy link
Contributor Author

abidh commented Nov 12, 2024

DW_TAG_dynamic_type

he fir.class type is used for polymorphic entities (including unlimited polymorphic entities, CLASS(*)).

Does DWARF provide support for querying the dynamic type at runtime?

I saw dwarf has a DW_TAG_dynamic_type tag, but that seems related to POINTER/ALLOCATABLE length parametrized derived types rather than polymorphism.

Sorry I missed this comment earlier. I agree that DW_TAG_dynamic_type does not seem to be intended for polymorphism. I have noticed that gfortran uses an artificial type in such cases which have a data, vptr and len field. I was thinking that we can also use a type that exposes elem_len and type field from the descriptor.

(gdb) ptype p_t2
type = Type __class__STAR_p
    PTR TO -> ( void :: _data )
    PTR TO -> ( Type __vtype__STAR :: _vptr )
    integer(kind=8) :: _len
End Type __class__STAR_p

Comment on lines 637 to 640
if (mlir::isa<fir::ClassType>(Ty))
return convertPointerLikeType(boxTy.unwrapInnerType(), fileAttr, scope,
declOp, /*genAllocated=*/false,
/*genAssociated=*/true);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I do not get why ClassType is bypassing the handling below made for BoxType and why genAssociated would be always true for them even when they are not POINTERs.

With the exception of the polymorphic aspect (the fact that the dynamic type may be an extension of the static type), which seems not straightforward to represent in DWARF, a fir.class is just like a fir.box. So the representation of the static type of a fir.box and fir.class should be the the same I think.

My comment about CLASS(*) may have mislead you here. I think it is OK to decide and agree how to represent those in a distinct patch, especially because they are also TYPE(*), so fir.box<none> is also a thing.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have changed the code so that ClassType is handled like any other BaseBoxType. As its underlying type can be a RecordType which was not being handled inside that if, I modified the default inside that if to be a pointer to underlying type instead of a place holder type.

This PR adds the handling of ClassType. It is treated as pointer to
the underlying type. Note that ClassType when passed to the function
have double indirection so it is represented as pointer to type
(and not type as a RecordType will be for example).

If ClassType wraps a pointer or allocatable then we take care to
avoid double indirection.

This is how it looks like in the debugger.

subroutine test_proc (this)
    class(test_type), intent (inout) :: this
    allocate (this%b (3, 2))
    call fill_array_2d (this%b)
    print *, this%a
end

(gdb) p this
$6 = (PTR TO -> ( Type test_type )) 0x2052a0
(gdb) p this%a
$7 = 0
(gdb) p this%b
$8 = ((1, 2, 3) (4, 5, 6))
(gdb)
Merged handling of ClassType with BoxType. Used boxTy.unwrapInnerType()
to avoid special casing for pointer or allocatable. Also improved
convertPointerLikeType to better handle if underlying type in null or
none.
Dont special case ClassTyp but handle it like a BoxType. Also in default case, generate a pointer to underlying type instead of a PlaceHolder type.
Copy link
Contributor

@jeanPerier jeanPerier left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, thanks

Copy link
Contributor

@tblah tblah left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@abidh abidh merged commit 030179c into llvm:main Nov 18, 2024
8 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
flang:fir-hlfir flang Flang issues not falling into any other category
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants