diff --git a/flang/docs/PolymorphicEntities.md b/flang/docs/PolymorphicEntities.md --- a/flang/docs/PolymorphicEntities.md +++ b/flang/docs/PolymorphicEntities.md @@ -55,10 +55,8 @@ `!fir.class` is a new type introduced for polymorphic entities. It's similar to a box type but allows the distinction between a monomorphic and a polymorphic descriptor. -A specific `BoxTypeInterface` (TypeInterface) can be introduced to share the -same API for both types where it is necessary. `!fir.class` and `!fir.box` can -also be based on a same `BaseBoxType` similar to the `BaseMemRefType` done for -MemRef. +`!fir.class` and `!fir.box` are based on a same `BaseBoxType` similar to the +`BaseMemRefType` done for MemRef. **Fortran** ```fortran @@ -140,7 +138,7 @@ type is (point_3d) print*, a%x, a%y, a%z class default - print*, + print*,'default' end select ``` @@ -165,9 +163,9 @@ **FIR** ``` -fir.select_type %p : !fir.class> [ - #fir.class_is>, ^bb1, - #fir.type_is>, ^bb2, +fir.select_type %6 : !fir.class>> [ + #fir.class_is>, ^bb1, + #fir.type_is>, ^bb2, unit, ^bb3] ``` @@ -175,55 +173,81 @@ The testing of the dynamic type of the selector is done by calling runtime functions. -The runtime has two functions to compare dynamic types . Note that this two -functions _ignore_ the values of `KIND` type parameters. A version of these -functions that does not _ignore_ the value of the `KIND` type parameters will -be implemented for the `SELECT TYPE` type guards testing. +The runtime has two functions to compare dynamic types. Note that these two +functions _ignore_ the values of `KIND` type parameters. -Currently available functions for the `EXTENDS_TYPE_OF` and `SAME_TYPE_AS` -intrinsics (`flang/include/flang/Evaluate/type.h`). +The functions for the `EXTENDS_TYPE_OF` and `SAME_TYPE_AS` +intrinsics (`flang/include/flang/Runtime/derived-api.h`). ```cpp -std::optional ExtendsTypeOf(const DynamicType &) const; -std::optional SameTypeAs(const DynamicType &) const; +// Perform the test of the SAME_TYPE_AS intrinsic. +bool RTNAME(SameTypeAs)(const Descriptor &, const Descriptor &); + +// Perform the test of the EXTENDS_TYPE_OF intrinsic. +bool RTNAME(ExtendsTypeOf)(const Descriptor &, const Descriptor &); +``` + +For the `SELECT TYPE` construct, the `KIND` type parameter is not ignored. The +`TYPE IS` type guard statement is lowered to an inlined comparison. The +`CLASS IS` type guard statement is lowered to a runtime function call. + +The function `ClassIs` implements the dynamic type comparison. +(`flang/include/flang/Runtime/derived-api.h`). +```cpp +// Perform the test of the CLASS IS type guard statement of the SELECT TYPE +// construct. +bool RTNAME(ClassIs)(const Descriptor &, const typeInfo::DerivedType &); ``` **FIR** (lower level FIR/MLIR after conversion to an if-then-else ladder) ``` module { - func @f(%arg0: !fir.class<*>) -> i32 { - %c4_i32 = arith.constant 4 : i32 - %c8_i32 = arith.constant 8 : i32 - %c16_i32 = arith.constant 16 : i32 - %0 = fir.gentypedesc !fir.tdesc>> - %1 = fir.convert %arg0 : (!fir.class>) -> !fir.box - %2 = fir.convert %0 : (!fir.tdesc>>) -> !fir.ref - %3 = fir.call @ExtendsTypeOfWithKind(%1, %2) : (!fir.box, !fir.ref) -> i1 - cond_br %3, ^bb2(%c4_i32 : i32), ^bb1 - ^bb1: // pred: ^bb0 - %4 = fir.gentypedesc !fir.type<_QTpoint_3d{x:f32,y:f32,z:f32}> - %5 = fir.convert %arg0 : (!fir.class>) -> !fir.box - %6 = fir.convert %4 : (!fir.tdesc>) -> !fir.ref - %7 = fir.call @SameTypeAsWithKind(%5, %6) : (!fir.box, !fir.ref) -> i1 - cond_br %7, ^bb4(%c16_i32 : i32), ^bb3 - ^bb2(%8: i32): // pred: ^bb0 - return %8 : i32 - ^bb3: // pred: ^bb1 - br ^bb5(%c8_i32 : i32) - ^bb4(%9: i32): // pred: ^bb1 - %10 = arith.addi %9, %9 : i32 - return %10 : i32 - ^bb5(%11: i32): // pred: ^bb3 - %12 = arith.muli %11, %11 : i32 - return %12 : i32 + func @f(%arg0: !fir.class>>) -> () { + // TYPE IS comparison done inlined. + %0 = fir.address_of(@_QFE.dt.point_3d) : !fir.ref> + %1 = fir.box_tdesc %arg0 : (!fir.class>>) -> !fir.tdesc + %2 = fir.convert %0 : (!fir.ref>) -> index + %3 = fir.convert %1 : (!fir.tdesc) -> index + %4 = arith.cmpi eq, %2, %3 : index + cf.cond_br %4, ^bb4, ^bb3 + ^bb1: // pred: ^bb3 + cf.br ^bb5 + ^bb2: // pred: ^bb3 + // CLASS IS block. + cf.br ^bb6 + ^bb3: // pred: ^bb0 + // CLASS IS comparison done with a runtime function call. + %24 = fir.address_of(@_QFE.dt.point) : !fir.ref> + %25 = fir.convert %24 : (!fir.ref>) -> !fir.ref + %26 = fir.convert %6 : (!fir.class>>) -> !fir.box + %27 = fir.call @_FortranAClassIs(%26, %25) : (!fir.box, !fir.ref) -> i1 + cf.cond_br %27, ^bb2, ^bb1 + ^bb4: // pred: ^bb0 + // TYPE IS block + cf.br ^bb6 + ^bb5: // pred: ^bb1 + // CLASS DEFAULT block. + cf.br ^bb6 + ^bb6: // 3 preds: ^bb2, ^bb4, ^bb5 + return } - func private @ExactSameTypeAsWithKind(!fir.box, !fir.ref) -> i1 - func private @SameTypeAsWithKind(!fir.box, !fir.ref) -> i1 + func.func private @_FortranAClassIs(!fir.box, !fir.ref) -> i1 } ``` -Note: some dynamic type checks can be inlined for performance. Type check with -intrinsic types when dealing with unlimited polymorphic entities is an ideal -candidate for inlined checks. +Dynamic type comparisons are inlined for performance whenever possible. +Dynamic type comparison for the `TYPE IS` type guard is inlined and +intrinsic types comparison when dealing with unlimited polymorphic entities are +also inlined. + +```fortran +type is (integer(4)) +``` + +``` +%i32typecode = arith.constant 9 : i8 +%typecode = fir.box_typecode %selector : (!fir.class) -> i8 +%isi32 = arith.cmpi eq, %typecode, %i32typecode : i8 +``` --- @@ -433,40 +457,50 @@ %_QM__fortran_builtinsT__builtin_c_funptr = type { i64 } ``` -The `fir.dispatch` is then lowered to use the runtime information to extract the +The `fir.dispatch` is lowered to FIR operations by the `PolymorphicOpConversion` +pass. It uses the runtime information to extract the correct function from the vtable and to perform the actual call. Here is what it can look like in pseudo LLVM IR code. +**FIR** +```c + %2 = fir.box_tdesc %arg0 : (!fir.class,base:f32,height:f32>>) -> !fir.tdesc + %3 = fir.box_tdesc %arg0 : (!fir.class>) -> !fir.tdesc + %4 = fir.convert %3 : (!fir.tdesc) -> !fir.ref> + %5 = fir.field_index binding, !fir.type<_QM__fortran_type_infoTderivedtype{}> + %6 = fir.coordinate_of %4, %5 : (!fir.ref>, !fir.field) -> !fir.ref>>>> + %7 = fir.load %6 : !fir.ref>>>> + %8 = fir.box_addr %7 : (!fir.box>>>) -> !fir.ptr>> + %c0 = arith.constant 0 : index + %9 = fir.coordinate_of %8, %c0 : (!fir.ptr> + %10 = fir.field_index proc, !fir.type<_QM__fortran_type_infoTbinding{proc:!fir.type<_QM__fortran_builtinsT__builtin_c_funptr{__address:i64}>,name:!fir.box>>}> + %11 = fir.coordinate_of %9, %10 : (!fir.ref>, !fir.field) -> !fir.ref> + %12 = fir.field_index __address, !fir.type<_QM__fortran_builtinsT__builtin_c_funptr{__address:i64}> + %13 = fir.coordinate_of %11, %12 : (!fir.ref>, !fir.field) -> !fir.ref + %14 = fir.load %13 : !fir.ref + %15 = fir.convert %14 : (i64) -> ((!fir.class>) -> ()) + fir.call %15(%arg0) : (!fir.class>) -> () +``` + **LLVMIR** ```c -// Retrieve the bindings (vtable) from the type information from the descriptor -%1 = call %_QM__fortran_type_infoTbinding* @_FortranAGetBindings(%desc) -// Retrieve the position of the specific bindings in the table -%2 = call i32 @_FortranAGetBindingOffset(%1, "get_area") -// Get the binding from the table -%3 = getelementptr %_QM__fortran_type_infoTbinding, %_QM__fortran_type_infoTbinding* %1, i32 0, i32 %2 -// Get the function pointer from the binding -%4 = getelementptr %_QM__fortran_builtinsT__builtin_c_funptr, %_QM__fortran_type_infoTbinding %3, i32 0, i32 0 -// Cast func pointer -%5 = inttoptr i64 %4 to -// Load the function -%6 = load f32(%_QMgeometryTshape*)*, %5 +// Retrieve the derived type runtime information and the vtable. +%14 = getelementptr %_QM__fortran_type_infoTderivedtype, ptr %13, i32 0, i32 0 +%15 = load { ptr, i64, i32, i8, i8, i8, i8, [1 x [3 x i64]], ptr, [1 x i64] }, ptr %14 +store { ptr, i64, i32, i8, i8, i8, i8, [1 x [3 x i64]], ptr, [1 x i64] } %15, ptr %8 +%16 = getelementptr { ptr, i64, i32, i8, i8, i8, i8, [1 x [3 x i64]], ptr, [1 x i64] }, ptr %8, i32 0, i32 0 +%17 = load ptr, ptr %16 +%18 = getelementptr %_QM__fortran_type_infoTbinding, ptr %17, i64 0 +%19 = getelementptr %_QM__fortran_type_infoTbinding, ptr %18, i32 0, i32 0 +%20 = getelementptr %_QM__fortran_builtinsT__builtin_c_funptr, ptr %19, i32 0, i32 0 +// Load func address +%21 = load i64, ptr %20 +// Cast to func pointer +%22 = inttoptr i64 %21 to ptr // Perform the actual function call -%7 = call f32 %6(%_QMgeometryTshape* %shape) +call void %22(ptr %0) ``` -_Note:_ functions `@_FortranAGetBindings` and `@_FortranAGetBindingOffset` are -not available in the runtime and will need to be implemented. - -- `@_FortranAGetBindings` retrieves the bindings from the descriptor. The - descriptor holds the type information that holds the bindings. -- `@_FortranAGetBindingOffset` retrieves the procedure offset in the bindings - based on the binding name provided. - -Retrieving the binding table and the offset are done separately so multiple -dynamic dispatch on the same polymorphic entities can be optimized (the binding -table is retrieved only once for multiple call). - ### Passing polymorphic entities as argument **Fortran** @@ -850,16 +884,10 @@ # Current TODOs Current list of TODOs in lowering: -- `flang/lib/Lower/Allocatable.cpp:465` not yet implemented: SOURCE allocation -- `flang/lib/Lower/Allocatable.cpp:468` not yet implemented: MOLD allocation - `flang/lib/Lower/Bridge.cpp:448` not yet implemented: create polymorphic host associated copy - `flang/lib/Lower/CallInterface.cpp:795` not yet implemented: support for polymorphic types - `flang/lib/Lower/ConvertType.cpp:237` not yet implemented: support for polymorphic types -Current list of TODOs in code generation: - -- `flang/lib/Optimizer/CodeGen/CodeGen.cpp:2651` not yet implemented: fir.gentypedesc codegen - --- Resources: