1//===-- PolymorphicOpConversion.cpp ---------------------------------------===//
2//
3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4// See https://llvm.org/LICENSE.txt for license information.
5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6//
7//===----------------------------------------------------------------------===//
8
9#include "flang/Lower/BuiltinModules.h"
10#include "flang/Optimizer/Builder/Todo.h"
11#include "flang/Optimizer/Dialect/FIRDialect.h"
12#include "flang/Optimizer/Dialect/FIROps.h"
13#include "flang/Optimizer/Dialect/FIROpsSupport.h"
14#include "flang/Optimizer/Dialect/FIRType.h"
15#include "flang/Optimizer/Dialect/Support/FIRContext.h"
16#include "flang/Optimizer/Dialect/Support/KindMapping.h"
17#include "flang/Optimizer/Support/InternalNames.h"
18#include "flang/Optimizer/Support/TypeCode.h"
19#include "flang/Optimizer/Support/Utils.h"
20#include "flang/Optimizer/Transforms/Passes.h"
21#include "flang/Runtime/derived-api.h"
22#include "flang/Semantics/runtime-type-info.h"
23#include "mlir/Dialect/Affine/IR/AffineOps.h"
24#include "mlir/Dialect/Arith/IR/Arith.h"
25#include "mlir/Dialect/ControlFlow/IR/ControlFlowOps.h"
26#include "mlir/Dialect/Func/IR/FuncOps.h"
27#include "mlir/IR/BuiltinOps.h"
28#include "mlir/Pass/Pass.h"
29#include "mlir/Transforms/DialectConversion.h"
30#include "llvm/ADT/SmallSet.h"
31#include "llvm/Support/CommandLine.h"
32
33namespace fir {
34#define GEN_PASS_DEF_POLYMORPHICOPCONVERSION
35#include "flang/Optimizer/Transforms/Passes.h.inc"
36} // namespace fir
37
38using namespace fir;
39using namespace mlir;
40
41namespace {
42
43/// SelectTypeOp converted to an if-then-else chain
44///
45/// This lowers the test conditions to calls into the runtime.
46class SelectTypeConv : public OpConversionPattern<fir::SelectTypeOp> {
47public:
48 using OpConversionPattern<fir::SelectTypeOp>::OpConversionPattern;
49
50 SelectTypeConv(mlir::MLIRContext *ctx)
51 : mlir::OpConversionPattern<fir::SelectTypeOp>(ctx) {}
52
53 llvm::LogicalResult
54 matchAndRewrite(fir::SelectTypeOp selectType, OpAdaptor adaptor,
55 mlir::ConversionPatternRewriter &rewriter) const override;
56
57private:
58 // Generate comparison of type descriptor addresses.
59 mlir::Value genTypeDescCompare(mlir::Location loc, mlir::Value selector,
60 mlir::Type ty, mlir::ModuleOp mod,
61 mlir::PatternRewriter &rewriter) const;
62
63 llvm::LogicalResult genTypeLadderStep(mlir::Location loc,
64 mlir::Value selector,
65 mlir::Attribute attr, mlir::Block *dest,
66 std::optional<mlir::ValueRange> destOps,
67 mlir::ModuleOp mod,
68 mlir::PatternRewriter &rewriter,
69 fir::KindMapping &kindMap) const;
70
71 llvm::SmallSet<llvm::StringRef, 4> collectAncestors(fir::TypeInfoOp dt,
72 mlir::ModuleOp mod) const;
73};
74
75/// Lower `fir.dispatch` operation. A virtual call to a method in a dispatch
76/// table.
77struct DispatchOpConv : public OpConversionPattern<fir::DispatchOp> {
78 using OpConversionPattern<fir::DispatchOp>::OpConversionPattern;
79
80 DispatchOpConv(mlir::MLIRContext *ctx, const BindingTables &bindingTables)
81 : mlir::OpConversionPattern<fir::DispatchOp>(ctx),
82 bindingTables(bindingTables) {}
83
84 llvm::LogicalResult
85 matchAndRewrite(fir::DispatchOp dispatch, OpAdaptor adaptor,
86 mlir::ConversionPatternRewriter &rewriter) const override {
87 mlir::Location loc = dispatch.getLoc();
88
89 if (bindingTables.empty())
90 return emitError(loc) << "no binding tables found";
91
92 // Get derived type information.
93 mlir::Type declaredType =
94 fir::getDerivedType(dispatch.getObject().getType().getEleTy());
95 assert(mlir::isa<fir::RecordType>(declaredType) && "expecting fir.type");
96 auto recordType = mlir::dyn_cast<fir::RecordType>(declaredType);
97
98 // Lookup for the binding table.
99 auto bindingsIter = bindingTables.find(recordType.getName());
100 if (bindingsIter == bindingTables.end())
101 return emitError(loc)
102 << "cannot find binding table for " << recordType.getName();
103
104 // Lookup for the binding.
105 const BindingTable &bindingTable = bindingsIter->second;
106 auto bindingIter = bindingTable.find(dispatch.getMethod());
107 if (bindingIter == bindingTable.end())
108 return emitError(loc)
109 << "cannot find binding for " << dispatch.getMethod();
110 unsigned bindingIdx = bindingIter->second;
111
112 mlir::Value passedObject = dispatch.getObject();
113
114 auto module = dispatch.getOperation()->getParentOfType<mlir::ModuleOp>();
115 Type typeDescTy;
116 std::string typeDescName =
117 NameUniquer::getTypeDescriptorName(recordType.getName());
118 if (auto global = module.lookupSymbol<fir::GlobalOp>(typeDescName)) {
119 typeDescTy = global.getType();
120 }
121
122 // clang-format off
123 // Before:
124 // fir.dispatch "proc1"(%11 :
125 // !fir.class<!fir.heap<!fir.type<_QMpolyTp1{a:i32,b:i32}>>>)
126
127 // After:
128 // %12 = fir.box_tdesc %11 : (!fir.class<!fir.heap<!fir.type<_QMpolyTp1{a:i32,b:i32}>>>) -> !fir.tdesc<none>
129 // %13 = fir.convert %12 : (!fir.tdesc<none>) -> !fir.ref<!fir.type<_QM__fortran_type_infoTderivedtype>>
130 // %14 = fir.field_index binding, !fir.type<_QM__fortran_type_infoTderivedtype>
131 // %15 = fir.coordinate_of %13, %14 : (!fir.ref<!fir.type<_QM__fortran_type_infoTderivedtype>>, !fir.field) -> !fir.ref<!fir.box<!fir.ptr<!fir.array<?x!fir.type<_QM__fortran_type_infoTbinding>>>>>
132 // %bindings = fir.load %15 : !fir.ref<!fir.box<!fir.ptr<!fir.array<?x!fir.type<_QM__fortran_type_infoTbinding>>>>>
133 // %16 = fir.box_addr %bindings : (!fir.box<!fir.ptr<!fir.array<?x!fir.type<_QM__fortran_type_infoTbinding>>>>) -> !fir.ptr<!fir.array<?x!fir.type<_QM__fortran_type_infoTbinding>>>
134 // %17 = fir.coordinate_of %16, %c0 : (!fir.ptr<!fir.array<?x!fir.type<_QM__fortran_type_infoTbinding>>>, index) -> !fir.ref<!fir.type<_QM__fortran_type_infoTbinding>>
135 // %18 = fir.field_index proc, !fir.type<_QM__fortran_type_infoTbinding>
136 // %19 = fir.coordinate_of %17, %18 : (!fir.ref<!fir.type<_QM__fortran_type_infoTbinding>>, !fir.field) -> !fir.ref<!fir.type<_QM__fortran_builtinsT__builtin_c_funptr>>
137 // %20 = fir.field_index __address, !fir.type<_QM__fortran_builtinsT__builtin_c_funptr>
138 // %21 = fir.coordinate_of %19, %20 : (!fir.ref<!fir.type<_QM__fortran_builtinsT__builtin_c_funptr>>, !fir.field) -> !fir.ref<i64>
139 // %22 = fir.load %21 : !fir.ref<i64>
140 // %23 = fir.convert %22 : (i64) -> (() -> ())
141 // fir.call %23() : () -> ()
142 // clang-format on
143
144 // Load the descriptor.
145 mlir::Type fieldTy = fir::FieldType::get(rewriter.getContext());
146 mlir::Type tdescType =
147 fir::TypeDescType::get(mlir::NoneType::get(rewriter.getContext()));
148 mlir::Value boxDesc =
149 rewriter.create<fir::BoxTypeDescOp>(loc, tdescType, passedObject);
150 boxDesc = rewriter.create<fir::ConvertOp>(
151 loc, fir::ReferenceType::get(typeDescTy), boxDesc);
152
153 // Load the bindings descriptor.
154 auto bindingsCompName = Fortran::semantics::bindingDescCompName;
155 fir::RecordType typeDescRecTy = mlir::cast<fir::RecordType>(typeDescTy);
156 mlir::Value field = rewriter.create<fir::FieldIndexOp>(
157 loc, fieldTy, bindingsCompName, typeDescRecTy, mlir::ValueRange{});
158 mlir::Type coorTy =
159 fir::ReferenceType::get(typeDescRecTy.getType(bindingsCompName));
160 mlir::Value bindingBoxAddr =
161 rewriter.create<fir::CoordinateOp>(loc, coorTy, boxDesc, field);
162 mlir::Value bindingBox = rewriter.create<fir::LoadOp>(loc, bindingBoxAddr);
163
164 // Load the correct binding.
165 mlir::Value bindings = rewriter.create<fir::BoxAddrOp>(loc, bindingBox);
166 fir::RecordType bindingTy = fir::unwrapIfDerived(
167 mlir::cast<fir::BaseBoxType>(bindingBox.getType()));
168 mlir::Type bindingAddrTy = fir::ReferenceType::get(bindingTy);
169 mlir::Value bindingIdxVal = rewriter.create<mlir::arith::ConstantOp>(
170 loc, rewriter.getIndexType(), rewriter.getIndexAttr(bindingIdx));
171 mlir::Value bindingAddr = rewriter.create<fir::CoordinateOp>(
172 loc, bindingAddrTy, bindings, bindingIdxVal);
173
174 // Get the function pointer.
175 auto procCompName = Fortran::semantics::procCompName;
176 mlir::Value procField = rewriter.create<fir::FieldIndexOp>(
177 loc, fieldTy, procCompName, bindingTy, mlir::ValueRange{});
178 fir::RecordType procTy =
179 mlir::cast<fir::RecordType>(bindingTy.getType(procCompName));
180 mlir::Type procRefTy = fir::ReferenceType::get(procTy);
181 mlir::Value procRef = rewriter.create<fir::CoordinateOp>(
182 loc, procRefTy, bindingAddr, procField);
183
184 auto addressFieldName = Fortran::lower::builtin::cptrFieldName;
185 mlir::Value addressField = rewriter.create<fir::FieldIndexOp>(
186 loc, fieldTy, addressFieldName, procTy, mlir::ValueRange{});
187 mlir::Type addressTy = procTy.getType(addressFieldName);
188 mlir::Type addressRefTy = fir::ReferenceType::get(addressTy);
189 mlir::Value addressRef = rewriter.create<fir::CoordinateOp>(
190 loc, addressRefTy, procRef, addressField);
191 mlir::Value address = rewriter.create<fir::LoadOp>(loc, addressRef);
192
193 // Get the function type.
194 llvm::SmallVector<mlir::Type> argTypes;
195 for (mlir::Value operand : dispatch.getArgs())
196 argTypes.push_back(operand.getType());
197 llvm::SmallVector<mlir::Type> resTypes;
198 if (!dispatch.getResults().empty())
199 resTypes.push_back(dispatch.getResults()[0].getType());
200
201 mlir::Type funTy =
202 mlir::FunctionType::get(rewriter.getContext(), argTypes, resTypes);
203 mlir::Value funcPtr = rewriter.create<fir::ConvertOp>(loc, funTy, address);
204
205 // Make the call.
206 llvm::SmallVector<mlir::Value> args{funcPtr};
207 args.append(dispatch.getArgs().begin(), dispatch.getArgs().end());
208 rewriter.replaceOpWithNewOp<fir::CallOp>(
209 dispatch, resTypes, nullptr, args, dispatch.getArgAttrsAttr(),
210 dispatch.getResAttrsAttr(), dispatch.getProcedureAttrsAttr());
211 return mlir::success();
212 }
213
214private:
215 BindingTables bindingTables;
216};
217
218/// Convert FIR structured control flow ops to CFG ops.
219class PolymorphicOpConversion
220 : public fir::impl::PolymorphicOpConversionBase<PolymorphicOpConversion> {
221public:
222 llvm::LogicalResult initialize(mlir::MLIRContext *ctx) override {
223 return mlir::success();
224 }
225
226 void runOnOperation() override {
227 auto *context = &getContext();
228 mlir::ModuleOp mod = getOperation();
229 mlir::RewritePatternSet patterns(context);
230
231 BindingTables bindingTables;
232 buildBindingTables(bindingTables, mod);
233
234 patterns.insert<SelectTypeConv>(context);
235 patterns.insert<DispatchOpConv>(context, bindingTables);
236 mlir::ConversionTarget target(*context);
237 target.addLegalDialect<mlir::affine::AffineDialect,
238 mlir::cf::ControlFlowDialect, FIROpsDialect,
239 mlir::func::FuncDialect>();
240
241 // apply the patterns
242 target.addIllegalOp<SelectTypeOp>();
243 target.addIllegalOp<DispatchOp>();
244 target.markUnknownOpDynamicallyLegal([](Operation *) { return true; });
245 if (mlir::failed(mlir::applyPartialConversion(getOperation(), target,
246 std::move(patterns)))) {
247 mlir::emitError(mlir::UnknownLoc::get(context),
248 "error in converting to CFG\n");
249 signalPassFailure();
250 }
251 }
252};
253} // namespace
254
255llvm::LogicalResult SelectTypeConv::matchAndRewrite(
256 fir::SelectTypeOp selectType, OpAdaptor adaptor,
257 mlir::ConversionPatternRewriter &rewriter) const {
258 auto operands = adaptor.getOperands();
259 auto typeGuards = selectType.getCases();
260 unsigned typeGuardNum = typeGuards.size();
261 auto selector = selectType.getSelector();
262 auto loc = selectType.getLoc();
263 auto mod = selectType.getOperation()->getParentOfType<mlir::ModuleOp>();
264 fir::KindMapping kindMap = fir::getKindMapping(mod);
265
266 // Order type guards so the condition and branches are done to respect the
267 // Execution of SELECT TYPE construct as described in the Fortran 2018
268 // standard 11.1.11.2 point 4.
269 // 1. If a TYPE IS type guard statement matches the selector, the block
270 // following that statement is executed.
271 // 2. Otherwise, if exactly one CLASS IS type guard statement matches the
272 // selector, the block following that statement is executed.
273 // 3. Otherwise, if several CLASS IS type guard statements match the
274 // selector, one of these statements will inevitably specify a type that
275 // is an extension of all the types specified in the others; the block
276 // following that statement is executed.
277 // 4. Otherwise, if there is a CLASS DEFAULT type guard statement, the block
278 // following that statement is executed.
279 // 5. Otherwise, no block is executed.
280
281 llvm::SmallVector<unsigned> orderedTypeGuards;
282 llvm::SmallVector<unsigned> orderedClassIsGuards;
283 unsigned defaultGuard = typeGuardNum - 1;
284
285 // The following loop go through the type guards in the fir.select_type
286 // operation and sort them into two lists.
287 // - All the TYPE IS type guard are added in order to the orderedTypeGuards
288 // list. This list is used at the end to generate the if-then-else ladder.
289 // - CLASS IS type guard are added in a separate list. If a CLASS IS type
290 // guard type extends a type already present, the type guard is inserted
291 // before in the list to respect point 3. above. Otherwise it is just
292 // added in order at the end.
293 for (unsigned t = 0; t < typeGuardNum; ++t) {
294 if (auto a = mlir::dyn_cast<fir::ExactTypeAttr>(typeGuards[t])) {
295 orderedTypeGuards.push_back(Elt: t);
296 continue;
297 }
298
299 if (auto a = mlir::dyn_cast<fir::SubclassAttr>(typeGuards[t])) {
300 if (auto recTy = mlir::dyn_cast<fir::RecordType>(a.getType())) {
301 auto dt = mod.lookupSymbol<fir::TypeInfoOp>(recTy.getName());
302 assert(dt && "dispatch table not found");
303 llvm::SmallSet<llvm::StringRef, 4> ancestors =
304 collectAncestors(dt, mod);
305 if (!ancestors.empty()) {
306 auto it = orderedClassIsGuards.begin();
307 while (it != orderedClassIsGuards.end()) {
308 fir::SubclassAttr sAttr =
309 mlir::dyn_cast<fir::SubclassAttr>(typeGuards[*it]);
310 if (auto ty = mlir::dyn_cast<fir::RecordType>(sAttr.getType())) {
311 if (ancestors.contains(V: ty.getName()))
312 break;
313 }
314 ++it;
315 }
316 if (it != orderedClassIsGuards.end()) {
317 // Parent type is present so place it before.
318 orderedClassIsGuards.insert(I: it, Elt: t);
319 continue;
320 }
321 }
322 }
323 orderedClassIsGuards.push_back(Elt: t);
324 }
325 }
326 orderedTypeGuards.append(RHS: orderedClassIsGuards);
327 orderedTypeGuards.push_back(Elt: defaultGuard);
328 assert(orderedTypeGuards.size() == typeGuardNum &&
329 "ordered type guard size doesn't match number of type guards");
330
331 for (unsigned idx : orderedTypeGuards) {
332 auto *dest = selectType.getSuccessor(idx);
333 std::optional<mlir::ValueRange> destOps =
334 selectType.getSuccessorOperands(operands, idx);
335 if (mlir::dyn_cast<mlir::UnitAttr>(typeGuards[idx]))
336 rewriter.replaceOpWithNewOp<mlir::cf::BranchOp>(
337 selectType, dest, destOps.value_or(mlir::ValueRange{}));
338 else if (mlir::failed(genTypeLadderStep(loc, selector, typeGuards[idx],
339 dest, destOps, mod, rewriter,
340 kindMap)))
341 return mlir::failure();
342 }
343 return mlir::success();
344}
345
346llvm::LogicalResult SelectTypeConv::genTypeLadderStep(
347 mlir::Location loc, mlir::Value selector, mlir::Attribute attr,
348 mlir::Block *dest, std::optional<mlir::ValueRange> destOps,
349 mlir::ModuleOp mod, mlir::PatternRewriter &rewriter,
350 fir::KindMapping &kindMap) const {
351 mlir::Value cmp;
352 // TYPE IS type guard comparison are all done inlined.
353 if (auto a = mlir::dyn_cast<fir::ExactTypeAttr>(attr)) {
354 if (fir::isa_trivial(a.getType()) ||
355 mlir::isa<fir::CharacterType>(a.getType())) {
356 // For type guard statement with Intrinsic type spec the type code of
357 // the descriptor is compared.
358 int code = fir::getTypeCode(a.getType(), kindMap);
359 if (code == 0)
360 return mlir::emitError(loc)
361 << "type code unavailable for " << a.getType();
362 mlir::Value typeCode = rewriter.create<mlir::arith::ConstantOp>(
363 loc, rewriter.getI8IntegerAttr(code));
364 mlir::Value selectorTypeCode = rewriter.create<fir::BoxTypeCodeOp>(
365 loc, rewriter.getI8Type(), selector);
366 cmp = rewriter.create<mlir::arith::CmpIOp>(
367 loc, mlir::arith::CmpIPredicate::eq, selectorTypeCode, typeCode);
368 } else {
369 // Flang inline the kind parameter in the type descriptor so we can
370 // directly check if the type descriptor addresses are identical for
371 // the TYPE IS type guard statement.
372 mlir::Value res =
373 genTypeDescCompare(loc, selector, a.getType(), mod, rewriter);
374 if (!res)
375 return mlir::failure();
376 cmp = res;
377 }
378 // CLASS IS type guard statement is done with a runtime call.
379 } else if (auto a = mlir::dyn_cast<fir::SubclassAttr>(attr)) {
380 // Retrieve the type descriptor from the type guard statement record type.
381 assert(mlir::isa<fir::RecordType>(a.getType()) && "expect fir.record type");
382 fir::RecordType recTy = mlir::dyn_cast<fir::RecordType>(a.getType());
383 std::string typeDescName =
384 fir::NameUniquer::getTypeDescriptorName(recTy.getName());
385 auto typeDescGlobal = mod.lookupSymbol<fir::GlobalOp>(typeDescName);
386 auto typeDescAddr = rewriter.create<fir::AddrOfOp>(
387 loc, fir::ReferenceType::get(typeDescGlobal.getType()),
388 typeDescGlobal.getSymbol());
389 mlir::Type typeDescTy = ReferenceType::get(rewriter.getNoneType());
390 mlir::Value typeDesc =
391 rewriter.create<ConvertOp>(loc, typeDescTy, typeDescAddr);
392
393 // Prepare the selector descriptor for the runtime call.
394 mlir::Type descNoneTy = fir::BoxType::get(rewriter.getNoneType());
395 mlir::Value descSelector =
396 rewriter.create<ConvertOp>(loc, descNoneTy, selector);
397
398 // Generate runtime call.
399 llvm::StringRef fctName = RTNAME_STRING(ClassIs);
400 mlir::func::FuncOp callee;
401 {
402 // Since conversion is done in parallel for each fir.select_type
403 // operation, the runtime function insertion must be threadsafe.
404 auto runtimeAttr =
405 mlir::NamedAttribute(fir::FIROpsDialect::getFirRuntimeAttrName(),
406 mlir::UnitAttr::get(rewriter.getContext()));
407 callee =
408 fir::createFuncOp(rewriter.getUnknownLoc(), mod, fctName,
409 rewriter.getFunctionType({descNoneTy, typeDescTy},
410 rewriter.getI1Type()),
411 {runtimeAttr});
412 }
413 cmp = rewriter
414 .create<fir::CallOp>(loc, callee,
415 mlir::ValueRange{descSelector, typeDesc})
416 .getResult(0);
417 }
418
419 auto *thisBlock = rewriter.getInsertionBlock();
420 auto *newBlock =
421 rewriter.createBlock(dest->getParent(), mlir::Region::iterator(dest));
422 rewriter.setInsertionPointToEnd(thisBlock);
423 if (destOps.has_value())
424 rewriter.create<mlir::cf::CondBranchOp>(loc, cmp, dest, destOps.value(),
425 newBlock, std::nullopt);
426 else
427 rewriter.create<mlir::cf::CondBranchOp>(loc, cmp, dest, newBlock);
428 rewriter.setInsertionPointToEnd(newBlock);
429 return mlir::success();
430}
431
432// Generate comparison of type descriptor addresses.
433mlir::Value
434SelectTypeConv::genTypeDescCompare(mlir::Location loc, mlir::Value selector,
435 mlir::Type ty, mlir::ModuleOp mod,
436 mlir::PatternRewriter &rewriter) const {
437 assert(mlir::isa<fir::RecordType>(ty) && "expect fir.record type");
438 fir::RecordType recTy = mlir::dyn_cast<fir::RecordType>(ty);
439 std::string typeDescName =
440 fir::NameUniquer::getTypeDescriptorName(recTy.getName());
441 auto typeDescGlobal = mod.lookupSymbol<fir::GlobalOp>(typeDescName);
442 if (!typeDescGlobal)
443 return {};
444 auto typeDescAddr = rewriter.create<fir::AddrOfOp>(
445 loc, fir::ReferenceType::get(typeDescGlobal.getType()),
446 typeDescGlobal.getSymbol());
447 auto intPtrTy = rewriter.getIndexType();
448 mlir::Type tdescType =
449 fir::TypeDescType::get(mlir::NoneType::get(rewriter.getContext()));
450 mlir::Value selectorTdescAddr =
451 rewriter.create<fir::BoxTypeDescOp>(loc, tdescType, selector);
452 auto typeDescInt =
453 rewriter.create<fir::ConvertOp>(loc, intPtrTy, typeDescAddr);
454 auto selectorTdescInt =
455 rewriter.create<fir::ConvertOp>(loc, intPtrTy, selectorTdescAddr);
456 return rewriter.create<mlir::arith::CmpIOp>(
457 loc, mlir::arith::CmpIPredicate::eq, typeDescInt, selectorTdescInt);
458}
459
460llvm::SmallSet<llvm::StringRef, 4>
461SelectTypeConv::collectAncestors(fir::TypeInfoOp dt, mlir::ModuleOp mod) const {
462 llvm::SmallSet<llvm::StringRef, 4> ancestors;
463 while (auto parentName = dt.getIfParentName()) {
464 ancestors.insert(*parentName);
465 dt = mod.lookupSymbol<fir::TypeInfoOp>(*parentName);
466 assert(dt && "parent type info not generated");
467 }
468 return ancestors;
469}
470

source code of flang/lib/Optimizer/Transforms/PolymorphicOpConversion.cpp