| // Copyright 2021 The IREE Authors |
| // |
| // Licensed 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 <memory> |
| #include <utility> |
| |
| #include "iree/compiler/Codegen/Dialect/Codegen/IR/IREECodegenAttrs.h" |
| #include "iree/compiler/Dialect/HAL/Analysis/BindingLayout.h" |
| #include "iree/compiler/Dialect/HAL/Analysis/DeviceAnalysis.h" |
| #include "iree/compiler/Dialect/HAL/IR/HALDialect.h" |
| #include "iree/compiler/Dialect/HAL/IR/HALOps.h" |
| #include "iree/compiler/Dialect/HAL/Target/TargetBackend.h" |
| #include "iree/compiler/Dialect/HAL/Target/TargetRegistry.h" |
| #include "iree/compiler/Dialect/HAL/Transforms/Passes.h" |
| #include "iree/compiler/Dialect/Stream/IR/StreamOps.h" |
| #include "llvm/ADT/StringSet.h" |
| #include "llvm/Support/Debug.h" |
| #include "mlir/Dialect/Arith/IR/Arith.h" |
| #include "mlir/Dialect/Func/IR/FuncOps.h" |
| #include "mlir/IR/Attributes.h" |
| #include "mlir/IR/Builders.h" |
| #include "mlir/IR/BuiltinTypes.h" |
| #include "mlir/IR/Diagnostics.h" |
| #include "mlir/IR/IRMapping.h" |
| #include "mlir/Interfaces/FunctionInterfaces.h" |
| #include "mlir/Pass/Pass.h" |
| #include "mlir/Transforms/GreedyPatternRewriteDriver.h" |
| |
| #define DEBUG_TYPE "iree-hal-materialize-interfaces" |
| |
| namespace mlir::iree_compiler::IREE::HAL { |
| |
| #define GEN_PASS_DEF_MATERIALIZEINTERFACESPASS |
| #include "iree/compiler/Dialect/HAL/Transforms/Passes.h.inc" |
| |
| namespace { |
| |
| // Map of original SymbolRefAttr to a list of SymbolRefAttrs in variants marked |
| // with the executable target the export is assigned. |
| using ExportExpansions = DenseMap< |
| Attribute, |
| SmallVector<std::pair<Attribute, IREE::HAL::ExecutableTargetAttr>>>; |
| |
| // Map of operations (executables, dispatches, etc) to the executable targets |
| // required by those operations based on usage. If missing or empty the default |
| // set should be used. |
| using RequiredExecutableTargets = |
| DenseMap<Operation *, SetVector<IREE::HAL::ExecutableTargetAttr>>; |
| |
| //===----------------------------------------------------------------------===// |
| // Utilities |
| //===----------------------------------------------------------------------===// |
| |
| static SymbolRefAttr |
| makeExportSymbolRefAttr(IREE::HAL::ExecutableOp executableOp, |
| IREE::HAL::ExecutableVariantOp variantOp, |
| IREE::HAL::ExecutableExportOp exportOp) { |
| return SymbolRefAttr::get(executableOp.getNameAttr(), |
| { |
| FlatSymbolRefAttr::get(variantOp.getNameAttr()), |
| FlatSymbolRefAttr::get(exportOp.getNameAttr()), |
| }); |
| } |
| |
| static void setApplicableObjects(Operation *sourceOp, |
| IREE::HAL::ExecutableVariantOp targetOp) { |
| auto objectsAttr = sourceOp->getAttrOfType<IREE::HAL::ExecutableObjectsAttr>( |
| "hal.executable.objects"); |
| if (!objectsAttr) { |
| return; |
| } |
| auto objects = objectsAttr.getApplicableObjects(targetOp.getTarget()); |
| if (!objects) { |
| return; |
| } |
| targetOp.setObjectsAttr(*objects); |
| } |
| |
| template <typename ExecutableOpT, typename ExportOpT> |
| static void |
| buildRequiredExecutableTypeTargetsMap(ModuleOp moduleOp, |
| DeviceAnalysis &deviceAnalysis, |
| BindingLayoutAnalysis &layoutAnalysis, |
| RequiredExecutableTargets &resultMap) { |
| // NOTE: we build the map before we process it so that the addresses are |
| // stable. |
| for (auto executableOp : moduleOp.template getOps<ExecutableOpT>()) { |
| (void)resultMap[executableOp]; |
| for (auto exportOp : executableOp.template getOps<ExportOpT>()) { |
| for (auto dispatchOp : layoutAnalysis.getExportDispatches(exportOp)) { |
| (void)resultMap[dispatchOp]; |
| } |
| } |
| } |
| for (auto executableOp : moduleOp.template getOps<ExecutableOpT>()) { |
| auto &executableTargetAttrs = resultMap[executableOp]; |
| for (auto exportOp : executableOp.template getOps<ExportOpT>()) { |
| for (auto dispatchOp : layoutAnalysis.getExportDispatches(exportOp)) { |
| auto &dispatchTargetAttrs = resultMap[dispatchOp]; |
| deviceAnalysis.gatherRequiredExecutableTargets(dispatchOp, |
| dispatchTargetAttrs); |
| executableTargetAttrs.insert(dispatchTargetAttrs.begin(), |
| dispatchTargetAttrs.end()); |
| } |
| } |
| if (executableOp.isPublic()) { |
| // Public executables need all possible targets. |
| deviceAnalysis.gatherAllExecutableTargets(executableTargetAttrs); |
| } |
| } |
| } |
| |
| // Builds a map of executable and dispatch ops to the executable targets that |
| // may be required. |
| static RequiredExecutableTargets |
| buildRequiredExecutableTargetsMap(ModuleOp moduleOp, |
| DeviceAnalysis &deviceAnalysis, |
| BindingLayoutAnalysis &layoutAnalysis) { |
| RequiredExecutableTargets resultMap; |
| buildRequiredExecutableTypeTargetsMap<IREE::HAL::ExecutableSourceOp, |
| IREE::HAL::ExecutableExportOp>( |
| moduleOp, deviceAnalysis, layoutAnalysis, resultMap); |
| buildRequiredExecutableTypeTargetsMap<IREE::Stream::ExecutableOp, |
| IREE::Stream::ExecutableExportOp>( |
| moduleOp, deviceAnalysis, layoutAnalysis, resultMap); |
| return resultMap; |
| } |
| |
| // Updates the target entry point symbols of |dispatchOp| to the expanded set of |
| // variant exports in |exportExpansions|. |
| static void |
| updateDispatchTargets(IREE::Stream::CmdDispatchOp dispatchOp, |
| const ExportExpansions &exportExpansions, |
| RequiredExecutableTargets &requiredExecutableTargets) { |
| auto &requiredTargetAttrs = requiredExecutableTargets[dispatchOp]; |
| SmallVector<Attribute> newAttrs; |
| for (auto oldAttr : dispatchOp.getEntryPointRefs()) { |
| auto it = exportExpansions.find(oldAttr); |
| if (it == exportExpansions.end()) { |
| newAttrs.push_back(oldAttr); // preserve existing |
| continue; |
| } |
| for (auto [newAttr, targetAttr] : it->second) { |
| // Filter the new expansions to only those used by the dispatch (if we |
| // have a valid filter). |
| if (requiredTargetAttrs.empty()) { |
| newAttrs.push_back(newAttr); |
| } else if (requiredTargetAttrs.contains(targetAttr)) { |
| newAttrs.push_back(newAttr); |
| } |
| } |
| } |
| dispatchOp.setEntryPointsAttr( |
| ArrayAttr::get(dispatchOp.getContext(), newAttrs)); |
| } |
| |
| //===----------------------------------------------------------------------===// |
| // hal.executable.source materialization |
| //===----------------------------------------------------------------------===// |
| |
| static void materializeExecutableFromSourceOp( |
| IREE::HAL::ExecutableSourceOp sourceOp, |
| BindingLayoutAnalysis &layoutAnalysis, |
| RequiredExecutableTargets &requiredExecutableTargets) { |
| // Gather the required executable targets based on the dispatches to exports |
| // in the source op. |
| SmallVector<IREE::HAL::ExecutableTargetAttr> targetAttrs( |
| requiredExecutableTargets[sourceOp].getArrayRef()); |
| if (targetAttrs.empty()) { |
| return; |
| } |
| llvm::stable_sort(targetAttrs, [](auto lhs, auto rhs) { |
| return lhs.getSymbolNameFragment() < rhs.getSymbolNameFragment(); |
| }); |
| |
| // Create the op that will contain the translated executable. |
| OpBuilder moduleBuilder(sourceOp); |
| auto executableOp = moduleBuilder.create<IREE::HAL::ExecutableOp>( |
| sourceOp.getLoc(), sourceOp.getName()); |
| executableOp.setVisibility(sourceOp.getVisibility()); |
| |
| // With this hand-authored path all variants have the same layout and entry |
| // points and we can just clone them. |
| auto sourceExportOps = sourceOp.getExportOps(); |
| |
| // Materialize all of the hal.executable.variant ops for all backends we are |
| // targeting. |
| ExportExpansions exportExpansions; |
| SymbolTable targetSymbolTable(executableOp); |
| OpBuilder targetBuilder(&executableOp.getBlock().back()); |
| for (auto targetAttr : targetAttrs) { |
| // Create new variant and clone the exports. |
| auto targetVariantOp = targetBuilder.create<IREE::HAL::ExecutableVariantOp>( |
| sourceOp->getLoc(), targetAttr.getSymbolNameFragment(), targetAttr); |
| targetSymbolTable.insert(targetVariantOp); |
| OpBuilder variantBuilder(&targetVariantOp.getBlock().back()); |
| for (auto sourceExportOp : sourceExportOps) { |
| variantBuilder.clone(*sourceExportOp); |
| |
| // Map the original export names to the new variant exports. |
| auto oldRefAttr = SymbolRefAttr::get( |
| executableOp.getNameAttr(), |
| {FlatSymbolRefAttr::get(sourceExportOp.getNameAttr())}); |
| auto newRefAttr = makeExportSymbolRefAttr(executableOp, targetVariantOp, |
| sourceExportOp); |
| exportExpansions[oldRefAttr].push_back( |
| std::make_pair(newRefAttr, targetAttr)); |
| } |
| |
| // Clone any target-specific object files specified. |
| if (auto objectsAttr = sourceOp.getObjectsAttr()) { |
| auto objects = objectsAttr.getApplicableObjects(targetAttr); |
| if (objects) { |
| targetVariantOp.setObjectsAttr(*objects); |
| } |
| } |
| |
| // Clone inner module contents. |
| if (!sourceOp.isExternal()) { |
| auto sourceModuleOp = sourceOp.getInnerModule(); |
| variantBuilder.clone(*sourceModuleOp); |
| } |
| } |
| |
| // Update all dispatch sites to reference the new expanded variants. |
| for (auto exportOp : sourceExportOps) { |
| for (auto dispatchOp : layoutAnalysis.getExportDispatches(exportOp)) { |
| updateDispatchTargets(dispatchOp, exportExpansions, |
| requiredExecutableTargets); |
| } |
| } |
| |
| // Remove the original. |
| sourceOp.erase(); |
| } |
| |
| //===----------------------------------------------------------------------===// |
| // Interface definition |
| //===----------------------------------------------------------------------===// |
| |
| // Verifies that all types used with the given entry point are supportable. |
| static LogicalResult |
| verifyEntryPointTypes(mlir::FunctionOpInterface entryFuncOp) { |
| for (auto inputType : llvm::enumerate(entryFuncOp.getArgumentTypes())) { |
| if (llvm::isa<IREE::Stream::BindingType>(inputType.value()) || |
| inputType.value().isInteger(32)) { |
| // OK - directly translates to a HAL interface binding. |
| } else { |
| return entryFuncOp.emitError() |
| << "unsupported interface function argument " << inputType.index() |
| << " type " << inputType.value() |
| << "; requires !stream.binding or i32 operands only"; |
| } |
| } |
| return success(); |
| } |
| |
| // Creates an pipeline layout attr from the analysis results. |
| static IREE::HAL::PipelineLayoutAttr |
| makePipelineLayoutAttr(const PipelineLayout &pipelineLayout, |
| IREE::HAL::ExecutableTargetAttr targetAttr, |
| OpBuilder &builder) { |
| SmallVector<IREE::HAL::PipelineBindingAttr> bindingAttrs; |
| for (const auto &binding : pipelineLayout.bindings) { |
| bindingAttrs.push_back(IREE::HAL::PipelineBindingAttr::get( |
| builder.getContext(), binding.type, binding.flags)); |
| } |
| return IREE::HAL::PipelineLayoutAttr::get(builder.getContext(), bindingAttrs, |
| pipelineLayout.constantCount, |
| pipelineLayout.flags); |
| } |
| |
| // Converts the usage of the given primitive |arg| to interface methods. |
| static void |
| convertOperandUsage(mlir::FunctionOpInterface sourceFuncOp, BlockArgument arg, |
| IREE::HAL::PipelineLayoutAttr pipelineLayoutAttr, |
| unsigned pushConstantIdx, OpBuilder &builder) { |
| auto alignmentAttr = sourceFuncOp.getArgAttrOfType<IntegerAttr>( |
| arg.getArgNumber(), "stream.alignment"); |
| auto valuesAttr = sourceFuncOp.getArgAttrOfType<ArrayAttr>(arg.getArgNumber(), |
| "stream.values"); |
| auto loadOp = builder.create<IREE::HAL::InterfaceConstantLoadOp>( |
| arg.getLoc(), arg.getType(), pipelineLayoutAttr, |
| builder.getIndexAttr(pushConstantIdx), alignmentAttr, valuesAttr); |
| arg.replaceAllUsesWith(loadOp); |
| } |
| |
| // Converts the usage of the given !stream.binding |arg| to interface methods. |
| static void |
| convertBindingUsage(mlir::FunctionOpInterface sourceFuncOp, BlockArgument arg, |
| IREE::HAL::PipelineLayoutAttr pipelineLayoutAttr, |
| int64_t bindingOrdinal, |
| IREE::HAL::PipelineBindingAttr bindingAttr) { |
| if (arg.use_empty()) |
| return; // no-op |
| for (auto &use : llvm::make_early_inc_range(arg.getUses())) { |
| auto oldOp = dyn_cast<IREE::Stream::BindingSubspanOp>(use.getOwner()); |
| assert(oldOp && "bindings are only usable by stream.binding.subspan"); |
| OpBuilder builder(oldOp); |
| auto alignmentAttr = sourceFuncOp.getArgAttrOfType<IntegerAttr>( |
| arg.getArgNumber(), "stream.alignment"); |
| auto newOp = builder.create<IREE::HAL::InterfaceBindingSubspanOp>( |
| oldOp.getLoc(), oldOp.getType(), pipelineLayoutAttr, |
| APInt(64, bindingOrdinal), oldOp.getByteOffset(), |
| oldOp.getDynamicDims(), alignmentAttr, bindingAttr.getFlags()); |
| oldOp.replaceAllUsesWith(newOp.getResult()); |
| oldOp.erase(); |
| } |
| } |
| |
| // Clones |sourceFuncOp| and updates its signature to match the |interfaceOp| |
| // and use the HAL interface access primitives. |
| static mlir::func::FuncOp |
| cloneFuncWithInterface(mlir::func::FuncOp sourceFuncOp, |
| const PipelineResourceMap &resourceMap, |
| IREE::HAL::PipelineLayoutAttr layoutAttr) { |
| // Clone so that we can do a bunch of unsafe in-place updates. |
| auto clonedFuncOp = sourceFuncOp.clone(); |
| |
| // Strip all arguments as functions take all I/O through the interface API. |
| clonedFuncOp.setType(FunctionType::get(clonedFuncOp.getContext(), {}, {})); |
| |
| auto *entryBlock = &clonedFuncOp.front(); |
| auto entryBuilder = OpBuilder::atBlockBegin(entryBlock); |
| |
| // Change the interface from arguments to hal.interface.* methods. |
| // We do push constant compatible operands first so that they are available |
| // for use by the binding accessors. |
| unsigned operandIdx = 0; |
| for (auto arg : entryBlock->getArguments()) { |
| if (!llvm::isa<IREE::Stream::BindingType>(arg.getType())) { |
| convertOperandUsage(sourceFuncOp, arg, layoutAttr, operandIdx++, |
| entryBuilder); |
| } |
| } |
| unsigned resourceIdx = 0; |
| for (auto arg : entryBlock->getArguments()) { |
| if (!llvm::isa<IREE::Stream::BindingType>(arg.getType())) { |
| continue; // unhandled arg type (primitive/etc) |
| } |
| auto binding = resourceMap[resourceIdx++]; |
| auto bindingAttr = layoutAttr.getBinding(binding); |
| assert(bindingAttr && "layout must be consistent"); |
| convertBindingUsage(sourceFuncOp, arg, layoutAttr, binding, bindingAttr); |
| } |
| |
| // Remove all arguments now that we've turned them into lookup ops. |
| entryBlock->eraseArguments([](auto arg) { return true; }); |
| |
| return clonedFuncOp; |
| } |
| |
| // Adds the entry point ops with assigned ordinals for each entry function. |
| // The entry points will all use the provided |interfaceOp| and be exported with |
| // hal.executable.export ops. |
| static LogicalResult |
| declareEntryPointOps(IREE::Stream::ExecutableOp sourceExecutableOp, |
| IREE::HAL::ExecutableOp targetExecutableOp, |
| const BindingLayoutAnalysis &layoutAnalysis, |
| RequiredExecutableTargets &requiredExecutableTargets) { |
| auto variantOps = |
| targetExecutableOp.getBlock().getOps<IREE::HAL::ExecutableVariantOp>(); |
| OpBuilder executableBuilder(&targetExecutableOp.getBlock().front()); |
| |
| // Build a map of source function definitions to their version with the |
| // updated interface per variant. |
| DenseMap<Operation *, DenseMap<IREE::HAL::ExecutableVariantOp, Operation *>> |
| targetFuncOps; |
| int nextOrdinal = 0; |
| for (auto exportOp : sourceExecutableOp.getBody() |
| .getOps<IREE::Stream::ExecutableExportOp>()) { |
| func::FuncOp sourceFuncOp; // optional, may be extern |
| if (auto sourceModuleOp = sourceExecutableOp.getInnerModule()) { |
| sourceFuncOp = sourceModuleOp.lookupSymbol<mlir::func::FuncOp>( |
| exportOp.getFunctionRef()); |
| if (failed(verifyEntryPointTypes(sourceFuncOp))) { |
| return failure(); |
| } |
| } |
| |
| // Lookup to see if a layout was specified already. If not we'll perform |
| // some basic analysis to come up with our own layout. |
| auto forcedLayoutAttr = |
| exportOp->getAttrOfType<IREE::HAL::PipelineLayoutAttr>( |
| "hal.interface.layout"); |
| const auto &pipelineLayout = layoutAnalysis.getPipelineLayout(exportOp); |
| const auto &resourceMap = pipelineLayout.resourceMap; |
| |
| // Clone the updated function declaration into each variant. |
| ExportExpansions exportExpansions; |
| int ordinal = nextOrdinal++; |
| for (auto variantOp : variantOps) { |
| auto targetBuilder = OpBuilder::atBlockBegin(&variantOp.getBlock()); |
| |
| // TODO(ravishankarm): use hal.interface.workgroup_size instead of codegen |
| // attributes. |
| // Check if workgroup size is set externally. |
| ArrayAttr workgroupSize; |
| for (auto attr : exportOp->getAttrs()) { |
| if (isa<IREE::Codegen::ExportConfigAttr>(attr.getValue())) { |
| workgroupSize = cast<IREE::Codegen::ExportConfigAttr>(attr.getValue()) |
| .getWorkgroupSizeIndexArray(); |
| if (workgroupSize.size() < 3) { |
| SmallVector<Attribute> workgroupSizeVals = |
| llvm::to_vector(workgroupSize); |
| workgroupSizeVals.resize(3, targetBuilder.getIndexAttr(1)); |
| workgroupSize = targetBuilder.getArrayAttr(workgroupSizeVals); |
| } |
| break; |
| } |
| } |
| |
| // Declare the entry point on the target. |
| auto variantLayoutAttr = |
| forcedLayoutAttr ? forcedLayoutAttr |
| : makePipelineLayoutAttr(pipelineLayout, |
| variantOp.getTargetAttr(), |
| targetBuilder); |
| auto newExportOp = targetBuilder.create<IREE::HAL::ExecutableExportOp>( |
| exportOp.getLoc(), |
| targetBuilder.getStringAttr(exportOp.getFunctionRef()), |
| targetBuilder.getIndexAttr(ordinal), variantLayoutAttr, workgroupSize, |
| /*subgroup_size=*/IntegerAttr{}, |
| /*workgroup_local_memory=*/IntegerAttr{}); |
| |
| // Map the original export name to the new variant export. |
| auto oldRefAttr = |
| SymbolRefAttr::get(sourceExecutableOp.getNameAttr(), |
| {FlatSymbolRefAttr::get(exportOp.getNameAttr())}); |
| auto newRefAttr = |
| makeExportSymbolRefAttr(targetExecutableOp, variantOp, newExportOp); |
| exportExpansions[oldRefAttr].push_back( |
| std::make_pair(newRefAttr, variantOp.getTargetAttr())); |
| |
| // Clone the workgroup count calculation function. |
| if (!exportOp.getWorkgroupCount().empty()) { |
| mlir::IRMapping mapper; |
| exportOp.getWorkgroupCount().cloneInto(&newExportOp.getWorkgroupCount(), |
| mapper); |
| // Insert the !hal.device argument if it doesn't already exist. |
| Type deviceType = targetBuilder.getType<IREE::HAL::DeviceType>(); |
| if (!llvm::is_contained(exportOp.getWorkgroupCount().getArgumentTypes(), |
| deviceType)) { |
| newExportOp.getWorkgroupCount().insertArgument(0u, deviceType, |
| newExportOp.getLoc()); |
| } |
| } |
| |
| // Clone the source function and update it to use the new interface. |
| if (sourceFuncOp) { |
| auto variantFuncOp = cloneFuncWithInterface(sourceFuncOp, resourceMap, |
| variantLayoutAttr); |
| targetFuncOps[sourceFuncOp][variantOp] = variantFuncOp; |
| } |
| } |
| |
| // Update all dispatch sites to reference the new expanded variants. |
| for (auto dispatchOp : layoutAnalysis.getExportDispatches(exportOp)) { |
| updateDispatchTargets(dispatchOp, exportExpansions, |
| requiredExecutableTargets); |
| } |
| } |
| |
| // Clone all of the ops in the source module to each variant. |
| // We'll use the exported functions with the updated interfaces in place of |
| // the original versions and copy everything else verbatim. |
| // Note that we do this as a cleanup setup because there may be multiple |
| // functions and multiple exports (with an N:M mapping) and in this way we |
| // perform the variant construction in a single pass with deterministic |
| // ordering that preserves the unmodified ops. |
| if (auto sourceModuleOp = sourceExecutableOp.getInnerModule()) { |
| for (auto variantOp : variantOps) { |
| auto targetBuilder = OpBuilder::atBlockBegin( |
| &variantOp.getInnerModule().getBodyRegion().front()); |
| for (auto &op : sourceModuleOp.getOps()) { |
| auto targetVariantFuncOps = targetFuncOps.find(&op); |
| if (targetVariantFuncOps != targetFuncOps.end()) { |
| // Move the updated function into place. |
| auto variantFuncOp = targetVariantFuncOps->second[variantOp]; |
| targetBuilder.insert(variantFuncOp); |
| } else { |
| // Regular op (globals, external function declarations, etc). |
| targetBuilder.clone(op); |
| } |
| } |
| } |
| } |
| |
| return success(); |
| } |
| |
| //===----------------------------------------------------------------------===// |
| // flow.dispatch.* info op conversion |
| //===----------------------------------------------------------------------===// |
| |
| namespace { |
| |
| struct ConvertReturnPattern : public OpRewritePattern<IREE::Stream::ReturnOp> { |
| using OpRewritePattern<IREE::Stream::ReturnOp>::OpRewritePattern; |
| LogicalResult matchAndRewrite(IREE::Stream::ReturnOp op, |
| PatternRewriter &rewriter) const override { |
| rewriter.replaceOpWithNewOp<IREE::HAL::ReturnOp>(op, op.getOperands()); |
| return success(); |
| } |
| }; |
| |
| template <typename SrcOp, typename DstOp> |
| struct ConvertDispatchWorkgroupInfoPattern final |
| : public OpRewritePattern<SrcOp> { |
| using OpRewritePattern<SrcOp>::OpRewritePattern; |
| LogicalResult matchAndRewrite(SrcOp op, |
| PatternRewriter &rewriter) const override { |
| rewriter.replaceOpWithNewOp<DstOp>(op, op.getResult().getType(), |
| op.getDimensionAttr(), |
| /*upper_bound=*/nullptr); |
| return success(); |
| } |
| }; |
| |
| struct InlineConstantWorkgroupSizePattern |
| : public OpRewritePattern<IREE::HAL::InterfaceWorkgroupSizeOp> { |
| using OpRewritePattern<IREE::HAL::InterfaceWorkgroupSizeOp>::OpRewritePattern; |
| LogicalResult matchAndRewrite(IREE::HAL::InterfaceWorkgroupSizeOp sizeOp, |
| PatternRewriter &rewriter) const override { |
| // Lookup the entry point matching the parent. |
| auto funcOp = sizeOp->getParentOfType<mlir::func::FuncOp>(); |
| auto variantOp = funcOp->getParentOfType<IREE::HAL::ExecutableVariantOp>(); |
| auto exportOp = dyn_cast<IREE::HAL::ExecutableExportOp>( |
| SymbolTable::lookupSymbolIn(variantOp, funcOp.getName())); |
| assert(exportOp && |
| "must have an entry point corresponding to the parent func"); |
| auto workgroupSizeAttr = exportOp.getWorkgroupSizeAttr(); |
| if (!workgroupSizeAttr) { |
| return failure(); |
| } |
| |
| uint64_t dimIdx = sizeOp.getDimension().getZExtValue(); |
| auto dimAttr = workgroupSizeAttr[dimIdx]; |
| rewriter.replaceOpWithNewOp<arith::ConstantOp>( |
| sizeOp, rewriter.getIndexType(), cast<TypedAttr>(dimAttr)); |
| return success(); |
| } |
| }; |
| |
| } // namespace |
| |
| static LogicalResult |
| convertDispatchWorkgroupInfoOps(IREE::HAL::ExecutableOp executableOp) { |
| RewritePatternSet patterns(executableOp.getContext()); |
| patterns.insert< |
| ConvertReturnPattern, |
| ConvertDispatchWorkgroupInfoPattern<IREE::Stream::DispatchWorkgroupIDOp, |
| IREE::HAL::InterfaceWorkgroupIDOp>, |
| ConvertDispatchWorkgroupInfoPattern< |
| IREE::Stream::DispatchWorkgroupCountOp, |
| IREE::HAL::InterfaceWorkgroupCountOp>, |
| ConvertDispatchWorkgroupInfoPattern<IREE::Stream::DispatchWorkgroupSizeOp, |
| IREE::HAL::InterfaceWorkgroupSizeOp>, |
| InlineConstantWorkgroupSizePattern>(executableOp.getContext()); |
| return applyPatternsGreedily(executableOp, std::move(patterns)); |
| } |
| |
| //===----------------------------------------------------------------------===// |
| // --iree-hal-materialize-interfaces |
| //===----------------------------------------------------------------------===// |
| |
| struct MaterializeInterfacesPass |
| : public IREE::HAL::impl::MaterializeInterfacesPassBase< |
| MaterializeInterfacesPass> { |
| void runOnOperation() override { |
| auto moduleOp = getOperation(); |
| SymbolTable symbolTable(moduleOp); |
| BindingLayoutAnalysis layoutAnalysis(moduleOp, symbolTable); |
| |
| // Run required analysis passes. |
| DeviceAnalysis deviceAnalysis(moduleOp); |
| if (failed(deviceAnalysis.run())) { |
| return signalPassFailure(); |
| } |
| |
| // If no devices were defined and there are dispatches in the program then |
| // error out. This provides a better error message than if we were to allow |
| // this pass to no-op and then fail during conversion later on. |
| if (layoutAnalysis.hasDispatches() && |
| deviceAnalysis.getDeviceGlobals().empty()) { |
| mlir::emitError(moduleOp.getLoc()) |
| << "no HAL devices defined in the module; use the module-level " |
| "hal.device.targets attribute, the --iree-hal-target-device= " |
| "flag, or provide inputs with global !hal.devices defined"; |
| return signalPassFailure(); |
| } |
| |
| // Gather the required executable targets per executable and dispatch site. |
| auto requiredExecutableTargets = buildRequiredExecutableTargetsMap( |
| moduleOp, deviceAnalysis, layoutAnalysis); |
| |
| // Handle any hand-authored executables; these only need variant expansion |
| // and no layout analysis as the user specified the layout themselves. |
| for (auto sourceOp : llvm::make_early_inc_range( |
| moduleOp.getOps<IREE::HAL::ExecutableSourceOp>())) { |
| materializeExecutableFromSourceOp(sourceOp, layoutAnalysis, |
| requiredExecutableTargets); |
| } |
| |
| // Processes all executables within the input module and produce the |
| // output HAL ops. We should ensure all deduping is performed prior to |
| // this when it's easier to diff IR and where we still have the flow |
| // context. |
| for (auto sourceOp : llvm::make_early_inc_range( |
| moduleOp.getOps<IREE::Stream::ExecutableOp>())) { |
| auto exportOps = sourceOp.getOps<IREE::Stream::ExecutableExportOp>(); |
| if (exportOps.empty()) { |
| continue; |
| } |
| |
| // Gather a list of all #hal.executable.targets that we should produce |
| // variants for based on the dispatches performed. Not all exports may be |
| // used on any particular target but we let future DCE/pruning passes |
| // remove them instead of modifying the inner modules here. |
| SmallVector<IREE::HAL::ExecutableTargetAttr> targetAttrs( |
| requiredExecutableTargets[sourceOp].getArrayRef()); |
| if (targetAttrs.empty()) { |
| return; |
| } |
| llvm::stable_sort(targetAttrs, [](auto lhs, auto rhs) { |
| return lhs.getSymbolNameFragment() < rhs.getSymbolNameFragment(); |
| }); |
| |
| // Create the op that will contain the translated executable. |
| OpBuilder builder = OpBuilder::atBlockEnd(moduleOp.getBody()); |
| builder.setInsertionPointAfter(sourceOp); |
| auto executableOp = builder.create<IREE::HAL::ExecutableOp>( |
| sourceOp.getLoc(), sourceOp.getName()); |
| executableOp.setVisibility(sourceOp.getVisibility()); |
| |
| // Materialize all of the hal.executable.variant ops for all backends we |
| // are targeting. |
| SymbolTable targetSymbolTable(executableOp); |
| OpBuilder targetBuilder(&executableOp.getBlock().back()); |
| for (auto targetAttr : targetAttrs) { |
| auto targetContainerOp = |
| targetBuilder.create<IREE::HAL::ExecutableVariantOp>( |
| sourceOp->getLoc(), targetAttr.getSymbolNameFragment(), |
| targetAttr); |
| setApplicableObjects(sourceOp, targetContainerOp); |
| targetSymbolTable.insert(targetContainerOp); |
| if (sourceOp.getInnerModule()) { |
| OpBuilder containerBuilder(&targetContainerOp.getBlock().back()); |
| containerBuilder.create<mlir::ModuleOp>(sourceOp->getLoc()); |
| } |
| } |
| |
| // Define interfaces for each exported function based on analysis. |
| if (failed(declareEntryPointOps(sourceOp, executableOp, layoutAnalysis, |
| requiredExecutableTargets))) { |
| return signalPassFailure(); |
| } |
| |
| // Convert interface-related stream.dispatch.* ops to their |
| // hal.interface.* versions. |
| if (failed(convertDispatchWorkgroupInfoOps(executableOp))) { |
| return signalPassFailure(); |
| } |
| |
| sourceOp.erase(); |
| } |
| } |
| }; |
| |
| } // namespace |
| |
| } // namespace mlir::iree_compiler::IREE::HAL |