| //===-- BoxedProcedure.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/Optimizer/CodeGen/CodeGen.h" | |
| #include "flang/Optimizer/Builder/FIRBuilder.h" | |
| #include "flang/Optimizer/Builder/LowLevelIntrinsics.h" | |
| #include "flang/Optimizer/Builder/Runtime/Trampoline.h" | |
| #include "flang/Optimizer/Dialect/FIROps.h" | |
| #include "flang/Optimizer/Dialect/FIRType.h" | |
| #include "flang/Optimizer/Dialect/Support/FIRContext.h" | |
| #include "flang/Optimizer/Support/FatalError.h" | |
| #include "flang/Optimizer/Support/InternalNames.h" | |
| #include "mlir/IR/PatternMatch.h" | |
| #include "mlir/Pass/Pass.h" | |
| #include "mlir/Transforms/DialectConversion.h" | |
| #include "llvm/ADT/DenseMap.h" | |
| #include "llvm/ADT/SmallVector.h" | |
| namespace fir { | |
| #define GEN_PASS_DEF_BOXEDPROCEDUREPASS | |
| #include "flang/Optimizer/CodeGen/CGPasses.h.inc" | |
| } // namespace fir | |
| #define DEBUG_TYPE "flang-procedure-pointer" | |
| using namespace fir; | |
| namespace { | |
| /// This type converter rewrites all `!fir.boxproc<Func>` types to `Func` types. | |
| class BoxprocTypeRewriter : public mlir::TypeConverter { | |
| public: | |
| using mlir::TypeConverter::convertType; | |
| /// Does the type \p ty need to be converted? | |
| /// Any type that is a `!fir.boxproc` in whole or in part will need to be | |
| /// converted to a function type to lower the IR to function pointer form in | |
| /// the default implementation performed in this pass. Other implementations | |
| /// are possible, so those may convert `!fir.boxproc` to some other type or | |
| /// not at all depending on the implementation target's characteristics and | |
| /// preference. | |
| bool needsConversion(mlir::Type ty) { | |
| if (mlir::isa<BoxProcType>(ty)) | |
| return true; | |
| if (auto funcTy = mlir::dyn_cast<mlir::FunctionType>(ty)) { | |
| for (auto t : funcTy.getInputs()) | |
| if (needsConversion(t)) | |
| return true; | |
| for (auto t : funcTy.getResults()) | |
| if (needsConversion(t)) | |
| return true; | |
| return false; | |
| } | |
| if (auto tupleTy = mlir::dyn_cast<mlir::TupleType>(ty)) { | |
| for (auto t : tupleTy.getTypes()) | |
| if (needsConversion(t)) | |
| return true; | |
| return false; | |
| } | |
| if (auto recTy = mlir::dyn_cast<RecordType>(ty)) { | |
| auto [visited, inserted] = visitedTypes.try_emplace(ty, false); | |
| if (!inserted) | |
| return visited->second; | |
| bool wasAlreadyVisitingRecordType = needConversionIsVisitingRecordType; | |
| needConversionIsVisitingRecordType = true; | |
| bool result = false; | |
| for (auto t : recTy.getTypeList()) { | |
| if (needsConversion(t.second)) { | |
| result = true; | |
| break; | |
| } | |
| } | |
| // Only keep the result cached if the fir.type visited was a "top-level | |
| // type". Nested types with a recursive reference to the "top-level type" | |
| // may incorrectly have been resolved as not needed conversions because it | |
| // had not been determined yet if the "top-level type" needed conversion. | |
| // This is not an issue to determine the "top-level type" need of | |
| // conversion, but the result should not be kept and later used in other | |
| // contexts. | |
| needConversionIsVisitingRecordType = wasAlreadyVisitingRecordType; | |
| if (needConversionIsVisitingRecordType) | |
| visitedTypes.erase(ty); | |
| else | |
| visitedTypes.find(ty)->second = result; | |
| return result; | |
| } | |
| if (auto boxTy = mlir::dyn_cast<BaseBoxType>(ty)) | |
| return needsConversion(boxTy.getEleTy()); | |
| if (isa_ref_type(ty)) | |
| return needsConversion(unwrapRefType(ty)); | |
| if (auto t = mlir::dyn_cast<SequenceType>(ty)) | |
| return needsConversion(unwrapSequenceType(ty)); | |
| if (auto t = mlir::dyn_cast<TypeDescType>(ty)) | |
| return needsConversion(t.getOfTy()); | |
| return false; | |
| } | |
| BoxprocTypeRewriter(mlir::Location location) : loc{location} { | |
| addConversion([](mlir::Type ty) { return ty; }); | |
| addConversion( | |
| [&](BoxProcType boxproc) { return convertType(boxproc.getEleTy()); }); | |
| addConversion([&](mlir::TupleType tupTy) { | |
| llvm::SmallVector<mlir::Type> memTys; | |
| for (auto ty : tupTy.getTypes()) | |
| memTys.push_back(convertType(ty)); | |
| return mlir::TupleType::get(tupTy.getContext(), memTys); | |
| }); | |
| addConversion([&](mlir::FunctionType funcTy) { | |
| llvm::SmallVector<mlir::Type> inTys; | |
| llvm::SmallVector<mlir::Type> resTys; | |
| for (auto ty : funcTy.getInputs()) | |
| inTys.push_back(convertType(ty)); | |
| for (auto ty : funcTy.getResults()) | |
| resTys.push_back(convertType(ty)); | |
| return mlir::FunctionType::get(funcTy.getContext(), inTys, resTys); | |
| }); | |
| addConversion([&](ReferenceType ty) { | |
| return ReferenceType::get(convertType(ty.getEleTy())); | |
| }); | |
| addConversion([&](PointerType ty) { | |
| return PointerType::get(convertType(ty.getEleTy())); | |
| }); | |
| addConversion( | |
| [&](HeapType ty) { return HeapType::get(convertType(ty.getEleTy())); }); | |
| addConversion([&](fir::LLVMPointerType ty) { | |
| return fir::LLVMPointerType::get(convertType(ty.getEleTy())); | |
| }); | |
| addConversion( | |
| [&](BoxType ty) { return BoxType::get(convertType(ty.getEleTy())); }); | |
| addConversion([&](ClassType ty) { | |
| return ClassType::get(convertType(ty.getEleTy())); | |
| }); | |
| addConversion([&](SequenceType ty) { | |
| // TODO: add ty.getLayoutMap() as needed. | |
| return SequenceType::get(ty.getShape(), convertType(ty.getEleTy())); | |
| }); | |
| addConversion([&](RecordType ty) -> mlir::Type { | |
| if (!needsConversion(ty)) | |
| return ty; | |
| if (auto converted = convertedTypes.lookup(ty)) | |
| return converted; | |
| auto rec = RecordType::get(ty.getContext(), | |
| ty.getName().str() + boxprocSuffix.str()); | |
| if (rec.isFinalized()) | |
| return rec; | |
| [[maybe_unused]] auto it = convertedTypes.try_emplace(ty, rec); | |
| assert(it.second && "expected ty to not be in the map"); | |
| std::vector<RecordType::TypePair> ps = ty.getLenParamList(); | |
| std::vector<RecordType::TypePair> cs; | |
| for (auto t : ty.getTypeList()) { | |
| if (needsConversion(t.second)) | |
| cs.emplace_back(t.first, convertType(t.second)); | |
| else | |
| cs.emplace_back(t.first, t.second); | |
| } | |
| rec.finalize(ps, cs); | |
| rec.pack(ty.isPacked()); | |
| return rec; | |
| }); | |
| addConversion([&](TypeDescType ty) { | |
| return TypeDescType::get(convertType(ty.getOfTy())); | |
| }); | |
| addSourceMaterialization(materializeProcedure); | |
| addTargetMaterialization(materializeProcedure); | |
| } | |
| static mlir::Value materializeProcedure(mlir::OpBuilder &builder, | |
| BoxProcType type, | |
| mlir::ValueRange inputs, | |
| mlir::Location loc) { | |
| assert(inputs.size() == 1); | |
| return ConvertOp::create(builder, loc, unwrapRefType(type.getEleTy()), | |
| inputs[0]); | |
| } | |
| void setLocation(mlir::Location location) { loc = location; } | |
| private: | |
| // Maps to deal with recursive derived types (avoid infinite loops). | |
| // Caching is also beneficial for apps with big types (dozens of | |
| // components and or parent types), so the lifetime of the cache | |
| // is the whole pass. | |
| llvm::DenseMap<mlir::Type, bool> visitedTypes; | |
| bool needConversionIsVisitingRecordType = false; | |
| llvm::DenseMap<mlir::Type, mlir::Type> convertedTypes; | |
| mlir::Location loc; | |
| }; | |
| /// A `boxproc` is an abstraction for a Fortran procedure reference. Typically, | |
| /// Fortran procedures can be referenced directly through a function pointer. | |
| /// However, Fortran has one-level dynamic scoping between a host procedure and | |
| /// its internal procedures. This allows internal procedures to directly access | |
| /// and modify the state of the host procedure's variables. | |
| /// | |
| /// There are any number of possible implementations possible. | |
| /// | |
| /// The implementation used here is to convert `boxproc` values to function | |
| /// pointers everywhere. If a `boxproc` value includes a frame pointer to the | |
| /// host procedure's data, then a thunk will be created at runtime to capture | |
| /// the frame pointer during execution. In LLVM IR, the frame pointer is | |
| /// designated with the `nest` attribute. The thunk's address will then be used | |
| /// as the call target instead of the original function's address directly. | |
| class BoxedProcedurePass | |
| : public fir::impl::BoxedProcedurePassBase<BoxedProcedurePass> { | |
| public: | |
| using BoxedProcedurePassBase<BoxedProcedurePass>::BoxedProcedurePassBase; | |
| inline mlir::ModuleOp getModule() { return getOperation(); } | |
| void runOnOperation() override final { | |
| if (useThunks) { | |
| auto *context = &getContext(); | |
| mlir::IRRewriter rewriter(context); | |
| BoxprocTypeRewriter typeConverter(mlir::UnknownLoc::get(context)); | |
| // When using safe trampolines, we need to track handles per | |
| // function so we can insert FreeTrampoline calls at each return. | |
| // Process functions individually to manage this state. | |
| if (useSafeTrampoline) { | |
| getModule().walk([&](mlir::func::FuncOp funcOp) { | |
| trampolineHandles.clear(); | |
| trampolineCallableMap.clear(); | |
| processFunction(funcOp, rewriter, typeConverter); | |
| insertTrampolineFrees(funcOp, rewriter); | |
| }); | |
| // Also process non-function ops at module level (globals, etc.) | |
| processModuleLevelOps(rewriter, typeConverter); | |
| } else { | |
| getModule().walk([&](mlir::Operation *op) { | |
| processOp(op, rewriter, typeConverter); | |
| }); | |
| } | |
| } | |
| } | |
| private: | |
| /// Trampoline handles collected while processing a function. | |
| /// Each entry is a Value representing the opaque handle returned | |
| /// by _FortranATrampolineInit, which must be freed before the | |
| /// function returns. | |
| llvm::SmallVector<mlir::Value> trampolineHandles; | |
| /// Cache of trampoline callable addresses keyed by the func SSA value | |
| /// of the emboxproc. This deduplicates trampolines when the same | |
| /// internal procedure is emboxed multiple times in one host function. | |
| llvm::DenseMap<mlir::Value, mlir::Value> trampolineCallableMap; | |
| /// Process all ops within a function. | |
| void processFunction(mlir::func::FuncOp funcOp, mlir::IRRewriter &rewriter, | |
| BoxprocTypeRewriter &typeConverter) { | |
| funcOp.walk( | |
| [&](mlir::Operation *op) { processOp(op, rewriter, typeConverter); }); | |
| } | |
| /// Process non-function ops at module level (globals, etc.) | |
| void processModuleLevelOps(mlir::IRRewriter &rewriter, | |
| BoxprocTypeRewriter &typeConverter) { | |
| for (auto &op : getModule().getBody()->getOperations()) | |
| if (!mlir::isa<mlir::func::FuncOp>(op)) | |
| processOp(&op, rewriter, typeConverter); | |
| } | |
| /// Insert _FortranATrampolineFree calls before every return in the function. | |
| void insertTrampolineFrees(mlir::func::FuncOp funcOp, | |
| mlir::IRRewriter &rewriter) { | |
| if (trampolineHandles.empty()) | |
| return; | |
| auto module{funcOp->getParentOfType<mlir::ModuleOp>()}; | |
| // Insert TrampolineFree calls before every func.return in this function. | |
| // At this pass stage (after CFGConversion), func.return is the only | |
| // terminator that exits the function. Other terminators are either | |
| // intra-function branches (cf.br, cf.cond_br, fir.select*) or | |
| // fir.unreachable (after STOP/ERROR STOP), which don't need cleanup | |
| // since the process is terminating. | |
| funcOp.walk([&](mlir::func::ReturnOp retOp) { | |
| rewriter.setInsertionPoint(retOp); | |
| FirOpBuilder builder(rewriter, module); | |
| auto loc{retOp.getLoc()}; | |
| for (mlir::Value handle : trampolineHandles) | |
| fir::runtime::genTrampolineFree(builder, loc, handle); | |
| }); | |
| } | |
| /// Process a single operation for boxproc type rewriting. | |
| void processOp(mlir::Operation *op, mlir::IRRewriter &rewriter, | |
| BoxprocTypeRewriter &typeConverter) { | |
| bool opIsValid{true}; | |
| typeConverter.setLocation(op->getLoc()); | |
| if (auto addr = mlir::dyn_cast<BoxAddrOp>(op)) { | |
| mlir::Type ty{addr.getVal().getType()}; | |
| mlir::Type resTy{addr.getResult().getType()}; | |
| if (llvm::isa<mlir::FunctionType>(ty) || | |
| llvm::isa<fir::BoxProcType>(ty)) { | |
| // Rewrite all `fir.box_addr` ops on values of type `!fir.boxproc` | |
| // or function type to be `fir.convert` ops. | |
| rewriter.setInsertionPoint(addr); | |
| rewriter.replaceOpWithNewOp<ConvertOp>( | |
| addr, typeConverter.convertType(addr.getType()), addr.getVal()); | |
| opIsValid = false; | |
| } else if (typeConverter.needsConversion(resTy)) { | |
| rewriter.startOpModification(op); | |
| op->getResult(0).setType(typeConverter.convertType(resTy)); | |
| rewriter.finalizeOpModification(op); | |
| } | |
| } else if (auto func = mlir::dyn_cast<mlir::func::FuncOp>(op)) { | |
| mlir::FunctionType ty{func.getFunctionType()}; | |
| if (typeConverter.needsConversion(ty)) { | |
| rewriter.startOpModification(func); | |
| auto toTy{ | |
| mlir::cast<mlir::FunctionType>(typeConverter.convertType(ty))}; | |
| if (!func.empty()) | |
| for (auto e : llvm::enumerate(toTy.getInputs())) { | |
| auto i{static_cast<unsigned>(e.index())}; | |
| auto &block{func.front()}; | |
| block.insertArgument(i, e.value(), func.getLoc()); | |
| block.getArgument(i + 1).replaceAllUsesWith(block.getArgument(i)); | |
| block.eraseArgument(i + 1); | |
| } | |
| func.setType(toTy); | |
| rewriter.finalizeOpModification(func); | |
| } | |
| } else if (auto embox = mlir::dyn_cast<EmboxProcOp>(op)) { | |
| // Rewrite all `fir.emboxproc` ops to either `fir.convert` or a thunk | |
| // as required. | |
| mlir::Type toTy{typeConverter.convertType( | |
| mlir::cast<BoxProcType>(embox.getType()).getEleTy())}; | |
| rewriter.setInsertionPoint(embox); | |
| if (embox.getHost()) { | |
| auto module{embox->getParentOfType<mlir::ModuleOp>()}; | |
| auto loc{embox.getLoc()}; | |
| if (useSafeTrampoline) { | |
| // Runtime trampoline pool path (W^X compliant). | |
| // Insert Init/Adjust in the function's entry block so the | |
| // handle dominates all func.return ops where TrampolineFree | |
| // is emitted. This is necessary because fir.emboxproc may | |
| // appear inside control flow branches. A cache avoids | |
| // creating duplicate trampolines for the same internal | |
| // procedure within a single host function. | |
| mlir::Value funcVal{embox.getFunc()}; | |
| auto cacheIt{trampolineCallableMap.find(funcVal)}; | |
| if (cacheIt != trampolineCallableMap.end()) { | |
| rewriter.replaceOpWithNewOp<ConvertOp>(embox, toTy, | |
| cacheIt->second); | |
| } else { | |
| auto parentFunc{embox->getParentOfType<mlir::func::FuncOp>()}; | |
| auto &entryBlock{parentFunc.front()}; | |
| auto savedIP{rewriter.saveInsertionPoint()}; | |
| // Find the right insertion point in the entry block. | |
| // Walk up from the emboxproc to find its top-level | |
| // ancestor in the entry block. For an emboxproc directly | |
| // in the entry block, this is the emboxproc itself. | |
| // For one inside a structured op (fir.if, fir.do_loop), | |
| // this is that structured op. For one inside an explicit | |
| // branch target (cf.cond_br → ^bb1), we fall back to the | |
| // entry block terminator. | |
| mlir::Operation *entryAncestor{embox.getOperation()}; | |
| while (entryAncestor->getBlock() != &entryBlock) { | |
| entryAncestor = entryAncestor->getParentOp(); | |
| if (!entryAncestor || | |
| mlir::isa<mlir::func::FuncOp>(entryAncestor)) | |
| break; | |
| } | |
| bool ancestorInEntry{ | |
| entryAncestor && | |
| !mlir::isa<mlir::func::FuncOp>(entryAncestor) && | |
| entryAncestor->getBlock() == &entryBlock}; | |
| // If the func value is not in the entry block (e.g., | |
| // address_of generated inside a structured fir.if), | |
| // clone it into the entry block. | |
| mlir::Value funcValInEntry{funcVal}; | |
| if (auto *funcDef{funcVal.getDefiningOp()}) { | |
| if (funcDef->getBlock() != &entryBlock) { | |
| if (ancestorInEntry) | |
| rewriter.setInsertionPoint(entryAncestor); | |
| else | |
| rewriter.setInsertionPoint(entryBlock.getTerminator()); | |
| auto *cloned{rewriter.clone(*funcDef)}; | |
| funcValInEntry = cloned->getResult(0); | |
| } | |
| } | |
| // The host link (closure pointer) must already be in the entry | |
| // block. In practice it is always either a function block argument | |
| // or an alloca emitted at function entry by the lowering — cloning | |
| // just the defining op would miss any stores that initialise it, | |
| // producing incorrect code. Assert that invariant rather than | |
| // attempting a broken clone. | |
| mlir::Value hostValInEntry{embox.getHost()}; | |
| if (auto *hostDef{embox.getHost().getDefiningOp()}) { | |
| if (hostDef->getBlock() != &entryBlock) { | |
| mlir::emitError(loc, | |
| "host link value is not defined in the entry " | |
| "block of the host function; cannot hoist " | |
| "TrampolineInit safely"); | |
| return; | |
| } | |
| } | |
| // Insert Init/Adjust at the determined position. | |
| FirOpBuilder builder(rewriter, module); | |
| if (ancestorInEntry) | |
| builder.setInsertionPoint(entryAncestor); | |
| else | |
| builder.setInsertionPoint(entryBlock.getTerminator()); | |
| mlir::Type i8Ty{builder.getI8Type()}; | |
| mlir::Type i8Ptr{builder.getRefType(i8Ty)}; | |
| mlir::Value nullPtr{builder.createNullConstant(loc, i8Ptr)}; | |
| mlir::Value closure{ | |
| builder.createConvert(loc, i8Ptr, hostValInEntry)}; | |
| mlir::Value func{builder.createConvert(loc, i8Ptr, funcValInEntry)}; | |
| // _FortranATrampolineInit(nullptr, func, closure) -> handle | |
| mlir::Value handle{fir::runtime::genTrampolineInit( | |
| builder, loc, nullPtr, func, closure)}; | |
| // _FortranATrampolineAdjust(handle) -> callable address | |
| mlir::Value callableAddr{ | |
| fir::runtime::genTrampolineAdjust(builder, loc, handle)}; | |
| trampolineHandles.push_back(handle); | |
| trampolineCallableMap[funcVal] = callableAddr; | |
| rewriter.restoreInsertionPoint(savedIP); | |
| rewriter.replaceOpWithNewOp<ConvertOp>(embox, toTy, callableAddr); | |
| } | |
| } else { | |
| // Legacy stack-based trampoline path. | |
| FirOpBuilder builder(rewriter, module); | |
| mlir::Type i8Ty{builder.getI8Type()}; | |
| mlir::Type i8Ptr{builder.getRefType(i8Ty)}; | |
| const auto triple{fir::getTargetTriple(module)}; | |
| // For PPC32 and PPC64, the thunk is populated by a call to | |
| // __trampoline_setup, which is defined in | |
| // compiler-rt/lib/builtins/trampoline_setup.c and requires the | |
| // thunk size greater than 32 bytes. For AArch64, RISCV and | |
| // x86_64, the thunk setup doesn't go through | |
| // __trampoline_setup and fits in 32 bytes. | |
| fir::SequenceType::Extent thunkSize{triple.getTrampolineSize()}; | |
| mlir::Type buffTy{SequenceType::get({thunkSize}, i8Ty)}; | |
| auto buffer{AllocaOp::create(builder, loc, buffTy)}; | |
| mlir::Value closure{ | |
| builder.createConvert(loc, i8Ptr, embox.getHost())}; | |
| mlir::Value tramp{builder.createConvert(loc, i8Ptr, buffer)}; | |
| mlir::Value func{builder.createConvert(loc, i8Ptr, embox.getFunc())}; | |
| fir::CallOp::create( | |
| builder, loc, factory::getLlvmInitTrampoline(builder), | |
| llvm::ArrayRef<mlir::Value>{tramp, func, closure}); | |
| auto adjustCall{fir::CallOp::create( | |
| builder, loc, factory::getLlvmAdjustTrampoline(builder), | |
| llvm::ArrayRef<mlir::Value>{tramp})}; | |
| rewriter.replaceOpWithNewOp<ConvertOp>(embox, toTy, | |
| adjustCall.getResult(0)); | |
| } | |
| opIsValid = false; | |
| } else { | |
| // Just forward the function as a pointer. | |
| rewriter.replaceOpWithNewOp<ConvertOp>(embox, toTy, embox.getFunc()); | |
| opIsValid = false; | |
| } | |
| } else if (auto global = mlir::dyn_cast<GlobalOp>(op)) { | |
| auto ty{global.getType()}; | |
| if (typeConverter.needsConversion(ty)) { | |
| rewriter.startOpModification(global); | |
| auto toTy{typeConverter.convertType(ty)}; | |
| global.setType(toTy); | |
| rewriter.finalizeOpModification(global); | |
| } | |
| } else if (auto mem = mlir::dyn_cast<AllocaOp>(op)) { | |
| auto ty{mem.getType()}; | |
| if (typeConverter.needsConversion(ty)) { | |
| rewriter.setInsertionPoint(mem); | |
| auto toTy{typeConverter.convertType(unwrapRefType(ty))}; | |
| bool isPinned{mem.getPinned()}; | |
| llvm::StringRef uniqName{mem.getUniqName().value_or(llvm::StringRef())}; | |
| llvm::StringRef bindcName{ | |
| mem.getBindcName().value_or(llvm::StringRef())}; | |
| rewriter.replaceOpWithNewOp<AllocaOp>(mem, toTy, uniqName, bindcName, | |
| isPinned, mem.getTypeparams(), | |
| mem.getShape()); | |
| opIsValid = false; | |
| } | |
| } else if (auto mem = mlir::dyn_cast<AllocMemOp>(op)) { | |
| auto ty{mem.getType()}; | |
| if (typeConverter.needsConversion(ty)) { | |
| rewriter.setInsertionPoint(mem); | |
| auto toTy{typeConverter.convertType(unwrapRefType(ty))}; | |
| llvm::StringRef uniqName{mem.getUniqName().value_or(llvm::StringRef())}; | |
| llvm::StringRef bindcName{ | |
| mem.getBindcName().value_or(llvm::StringRef())}; | |
| rewriter.replaceOpWithNewOp<AllocMemOp>(mem, toTy, uniqName, bindcName, | |
| mem.getTypeparams(), | |
| mem.getShape()); | |
| opIsValid = false; | |
| } | |
| } else if (auto coor = mlir::dyn_cast<CoordinateOp>(op)) { | |
| auto ty{coor.getType()}; | |
| mlir::Type baseTy{coor.getBaseType()}; | |
| if (typeConverter.needsConversion(ty) || | |
| typeConverter.needsConversion(baseTy)) { | |
| rewriter.setInsertionPoint(coor); | |
| auto toTy{typeConverter.convertType(ty)}; | |
| auto toBaseTy{typeConverter.convertType(baseTy)}; | |
| rewriter.replaceOpWithNewOp<CoordinateOp>(coor, toTy, coor.getRef(), | |
| coor.getCoor(), toBaseTy, | |
| coor.getFieldIndicesAttr()); | |
| opIsValid = false; | |
| } | |
| } else if (auto index = mlir::dyn_cast<FieldIndexOp>(op)) { | |
| auto ty{index.getType()}; | |
| mlir::Type onTy{index.getOnType()}; | |
| if (typeConverter.needsConversion(ty) || | |
| typeConverter.needsConversion(onTy)) { | |
| rewriter.setInsertionPoint(index); | |
| auto toTy{typeConverter.convertType(ty)}; | |
| auto toOnTy{typeConverter.convertType(onTy)}; | |
| rewriter.replaceOpWithNewOp<FieldIndexOp>( | |
| index, toTy, index.getFieldId(), toOnTy, index.getTypeparams()); | |
| opIsValid = false; | |
| } | |
| } else if (auto index = mlir::dyn_cast<LenParamIndexOp>(op)) { | |
| auto ty{index.getType()}; | |
| mlir::Type onTy{index.getOnType()}; | |
| if (typeConverter.needsConversion(ty) || | |
| typeConverter.needsConversion(onTy)) { | |
| rewriter.setInsertionPoint(index); | |
| auto toTy{typeConverter.convertType(ty)}; | |
| auto toOnTy{typeConverter.convertType(onTy)}; | |
| rewriter.replaceOpWithNewOp<LenParamIndexOp>( | |
| index, toTy, index.getFieldId(), toOnTy, index.getTypeparams()); | |
| opIsValid = false; | |
| } | |
| } else { | |
| rewriter.startOpModification(op); | |
| // Convert the operands if needed | |
| for (auto i : llvm::enumerate(op->getResultTypes())) | |
| if (typeConverter.needsConversion(i.value())) { | |
| auto toTy{typeConverter.convertType(i.value())}; | |
| op->getResult(i.index()).setType(toTy); | |
| } | |
| // Convert the type attributes if needed | |
| for (const mlir::NamedAttribute &attr : op->getAttrDictionary()) | |
| if (auto tyAttr = llvm::dyn_cast<mlir::TypeAttr>(attr.getValue())) | |
| if (typeConverter.needsConversion(tyAttr.getValue())) { | |
| auto toTy{typeConverter.convertType(tyAttr.getValue())}; | |
| op->setAttr(attr.getName(), mlir::TypeAttr::get(toTy)); | |
| } | |
| rewriter.finalizeOpModification(op); | |
| } | |
| // Ensure block arguments are updated if needed. | |
| if (opIsValid && op->getNumRegions() != 0) { | |
| rewriter.startOpModification(op); | |
| for (mlir::Region ®ion : op->getRegions()) | |
| for (mlir::Block &block : region.getBlocks()) | |
| for (mlir::BlockArgument blockArg : block.getArguments()) | |
| if (typeConverter.needsConversion(blockArg.getType())) { | |
| mlir::Type toTy{typeConverter.convertType(blockArg.getType())}; | |
| blockArg.setType(toTy); | |
| } | |
| rewriter.finalizeOpModification(op); | |
| } | |
| } | |
| }; | |
| } // namespace |