| // Copyright 2019 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 "iree/compiler/Dialect/HAL/Transforms/Passes.h" |
| |
| #include <memory> |
| |
| #include "iree/compiler/Dialect/HAL/IR/HALDialect.h" |
| #include "iree/compiler/Dialect/HAL/IR/HALOps.h" |
| #include "iree/compiler/Dialect/HAL/Target/Devices/LocalDevice.h" |
| #include "iree/compiler/Dialect/Util/Transforms/Passes.h" |
| #include "iree/compiler/Utils/OptionUtils.h" |
| #include "iree/compiler/Utils/PassUtils.h" |
| #include "mlir/Conversion/AffineToStandard/AffineToStandard.h" |
| #include "mlir/Conversion/SCFToControlFlow/SCFToControlFlow.h" |
| #include "mlir/Dialect/Affine/Passes.h" |
| #include "mlir/Dialect/Func/IR/FuncOps.h" |
| #include "mlir/Pass/PassRegistry.h" |
| #include "mlir/Transforms/Passes.h" |
| |
| namespace mlir::iree_compiler::IREE::HAL { |
| |
| namespace { |
| |
| struct TransformOptions : public PassPipelineOptions<TransformOptions> { |
| Option<bool> serializeExecutables{ |
| *this, |
| "serialize-executables", |
| llvm::cl::desc("Whether to serialize hal.executable.variant ops to " |
| "hal.executable.binary ops."), |
| llvm::cl::init(true), |
| }; |
| Option<bool> linkExecutables{ |
| *this, |
| "link-executables", |
| llvm::cl::desc("Whether to link hal.executable ops together."), |
| llvm::cl::init(true), |
| }; |
| }; |
| |
| static llvm::cl::opt<bool> clMemoization{ |
| "iree-hal-memoization", |
| llvm::cl::desc( |
| "Whether to memoize device resources such as command buffers."), |
| llvm::cl::init(true), |
| }; |
| |
| static llvm::cl::opt<unsigned> clBenchmarkDispatchRepeatCount{ |
| "iree-hal-benchmark-dispatch-repeat-count", |
| llvm::cl::desc( |
| "The number of times to repeat each hal.command_buffer.dispatch op. " |
| "This simply duplicates the dispatch op and inserts barriers. It's " |
| "meant for command buffers having linear dispatch structures."), |
| llvm::cl::init(1), |
| }; |
| |
| static llvm::cl::opt<llvm::cl::PowerOf2ByteSize> clInstrumentDispatchBufferSize{ |
| "iree-hal-instrument-dispatches", |
| llvm::cl::desc("Enables dispatch instrumentation with a power-of-two byte " |
| "size used for storage (16mib, 64mib, 2gib, etc)."), |
| llvm::cl::init(llvm::cl::PowerOf2ByteSize(0)), |
| }; |
| |
| static llvm::cl::list<std::string> clSubstituteExecutableSource{ |
| "iree-hal-substitute-executable-source", |
| llvm::cl::desc( |
| "A `executable_name=object_file.xxx` pair specifying a " |
| "hal.executable symbol name that will be substituted with the source " |
| "object file at the given path. Source object paths are relative to " |
| "those specified on `--iree-hal-executable-object-search-path=`. If a " |
| "`.mlir` or `.mlirbc` file is specified the entire executable will be " |
| "replaced with an equivalently named hal.executable in the referenced " |
| "file and otherwise the executable will be externalized and link the " |
| "referenced file (`.ptx`/`.spv`/etc)."), |
| }; |
| |
| static llvm::cl::opt<std::string> clSubstituteExecutableSourcesFrom{ |
| "iree-hal-substitute-executable-sources-from", |
| llvm::cl::desc( |
| "Substitutes any hal.executable with a file in the given path with " |
| "the same name ala `--iree-hal-substitute-executable-source=`."), |
| llvm::cl::init(""), |
| }; |
| |
| static llvm::cl::list<std::string> clSubstituteExecutableConfiguration{ |
| "iree-hal-substitute-executable-configuration", |
| llvm::cl::desc( |
| "A `executable_name=object_file.xxx` pair specifying a hal.executable " |
| "symbol name that will be substituted with the configured executable " |
| "file at the given path. Configured execuable paths are relative to " |
| "those specified on `--iree-hal-executable-object-search-path=`. If a " |
| "`.mlir` or `.mlirbc` file is specified the entire executable will be " |
| "replaced with an equivalently named hal.executable in the referenced " |
| "file and otherwise the executable will be externalized and link the " |
| "referenced file (`.ptx`/`.spv`/etc)."), |
| }; |
| |
| static llvm::cl::opt<std::string> clSubstituteExecutableConfigurationsFrom{ |
| "iree-hal-substitute-executable-configurations-from", |
| llvm::cl::desc( |
| "Substitutes any hal.executable with a file in the given path with " |
| "the same name ala `--iree-hal-substitute-executable-configuration=`."), |
| llvm::cl::init(""), |
| }; |
| |
| static llvm::cl::list<std::string> clSubstituteExecutableObject{ |
| "iree-hal-substitute-executable-object", |
| llvm::cl::desc( |
| "A `executable_name=object_file.xxx` pair specifying a " |
| "hal.executable symbol name that will be substituted with the object " |
| "file at the given path. Object paths are relative to those " |
| "specified on `--iree-hal-executable-object-search-path=`. If a " |
| "`.mlir` or `.mlirbc` file is specified the entire executable will be " |
| "replaced with an equivalently named hal.executable in the referenced " |
| "file and otherwise the executable will be externalized and link the " |
| "referenced file (`.ptx`/`.spv`/etc)."), |
| }; |
| |
| static llvm::cl::opt<std::string> clSubstituteExecutableObjectsFrom{ |
| "iree-hal-substitute-executable-objects-from", |
| llvm::cl::desc( |
| "Substitutes any hal.executable with a file in the given path with " |
| "the same name ala `--iree-hal-substitute-executable-object=`."), |
| llvm::cl::init(""), |
| }; |
| |
| static llvm::cl::list<std::string> clPreprocessExecutablesWith{ |
| "iree-hal-preprocess-executables-with", |
| llvm::cl::desc( |
| "Passes each hal.executable to the given command. Multiple " |
| "commands may be specified and they will be " |
| "executed in order. A command may either be a pass pipeline available " |
| "within the IREE compiler specified as `builtin.module(...)` or a " |
| "shell tool that consumes a hal.executable MLIR file on stdin and " |
| "produces a modified hal.executable on stdout. Non-zero exit codes " |
| "will fail compilation."), |
| }; |
| |
| static llvm::cl::opt<bool> clLinkExecutables{ |
| "iree-hal-link-executables", |
| llvm::cl::desc( |
| "Controls linking of executables. The default is to always link, " |
| "however disabling linking allows inspecting serialization " |
| "of each executable in isolation and will dump a single binary per " |
| "executable when used in conjunction with " |
| "`--iree-hal-dump-executable-binaries-to`."), |
| llvm::cl::init(true), |
| }; |
| |
| } // namespace |
| |
| using FunctionLikeNest = |
| MultiOpNest<func::FuncOp, IREE::Util::InitializerOp, IREE::Util::FuncOp>; |
| |
| //===----------------------------------------------------------------------===// |
| // Utilities |
| //===----------------------------------------------------------------------===// |
| |
| static void addCleanupPatterns(OpPassManager &passManager) { |
| |
| FunctionLikeNest(passManager) |
| // Standard MLIR cleanup. |
| .addPass(mlir::createCanonicalizerPass) |
| .addPass(mlir::createCSEPass) |
| |
| // Simplify util.global accesses; this can help with data flow tracking as |
| // redundant store-loads are removed. |
| .addPass(IREE::Util::createSimplifyGlobalAccessesPass) |
| |
| // Aggressive cleanup. |
| .addPass(IREE::Util::createApplyPatternsPass); |
| |
| // Cleanup and canonicalization of util.global (and other util ops). |
| passManager.addPass(IREE::Util::createFoldGlobalsPass()); |
| passManager.addPass(IREE::Util::createFuseGlobalsPass()); |
| } |
| |
| static void addExecutableSubstitutionPasses(OpPassManager &passManager, |
| ArrayRef<std::string> substitutions, |
| StringRef fromPath) { |
| if (!fromPath.empty()) { |
| SubstituteExecutablesPassOptions substituteOptions; |
| substituteOptions.searchPath = fromPath; |
| passManager.addPass( |
| IREE::HAL::createSubstituteExecutablesPass(substituteOptions)); |
| } |
| if (!substitutions.empty()) { |
| SubstituteExecutablesPassOptions substituteOptions; |
| substituteOptions.substitutions.assign(substitutions.begin(), |
| substitutions.end()); |
| passManager.addPass( |
| IREE::HAL::createSubstituteExecutablesPass(substituteOptions)); |
| } |
| } |
| |
| //===----------------------------------------------------------------------===// |
| // --iree-hal-device-assignment-pipeline |
| //===----------------------------------------------------------------------===// |
| |
| void buildHALDeviceAssignmentPassPipeline( |
| OpPassManager &passManager, const TargetRegistry &targetRegistry, |
| const AssignmentOptions &assignmentOptions) { |
| // The HAL must know its targets early on in the process. This pass discovers/ |
| // derives/specifies the target devices and annotates the module with that |
| // information. This allows subsequent passes to lookup which devices they are |
| // targeting. |
| if (!assignmentOptions.legacyTargetBackends.empty()) { |
| // Today we just assign devices from parameters but we should instead be |
| // performing analysis at the flow level and then doing magic device |
| // database lookups here. |
| AssignLegacyTargetDevicesPassOptions options; |
| options.targetRegistry = &targetRegistry; |
| options.targetBackends.assign( |
| assignmentOptions.legacyTargetBackends.begin(), |
| assignmentOptions.legacyTargetBackends.end()); |
| passManager.addPass( |
| IREE::HAL::createAssignLegacyTargetDevicesPass(options)); |
| } |
| if (!assignmentOptions.targetDevices.empty()) { |
| AssignTargetDevicesPassOptions options; |
| options.targetDevices.assign(assignmentOptions.targetDevices.begin(), |
| assignmentOptions.targetDevices.end()); |
| passManager.addPass(IREE::HAL::createAssignTargetDevicesPass(options)); |
| } |
| |
| // Create globals for each device (if needed). |
| passManager.addPass(IREE::HAL::createMaterializeTargetDevicesPass( |
| {assignmentOptions.defaultDevice})); |
| |
| // Resolve #hal.device.promise and #hal.device.alias attributes. |
| passManager.addPass(IREE::HAL::createResolveDevicePromisesPass()); |
| passManager.addPass( |
| IREE::HAL::createResolveDeviceAliasesPass({&targetRegistry})); |
| |
| // Verify devices are valid. |
| passManager.addPass(IREE::HAL::createVerifyDevicesPass({&targetRegistry})); |
| } |
| |
| //===----------------------------------------------------------------------===// |
| // --iree-hal-configuration-pipeline |
| //===----------------------------------------------------------------------===// |
| |
| void buildHALConfigurationPassPipeline(OpPassManager &passManager, |
| const TargetRegistry &targetRegistry, |
| const TargetOptions &targetOptions, |
| PipelineHooks hooks) { |
| //---------------------------------------------------------------------------- |
| // Input cleanup and simplification |
| //---------------------------------------------------------------------------- |
| |
| // Perform cleanup upon entry so that our IR is in a good state for assignment |
| // and initial interface analysis (we rely on CSE and such having been run). |
| addCleanupPatterns(passManager); |
| |
| // Verify devices are valid. |
| passManager.addPass(IREE::HAL::createVerifyDevicesPass({&targetRegistry})); |
| |
| //---------------------------------------------------------------------------- |
| // Device-specific interface materialization |
| //---------------------------------------------------------------------------- |
| |
| // Add dispatch instrumentation prior to materializing interfaces so we can |
| // more easily mutate the stream dispatch ops and exports. |
| if (auto bufferSize = clInstrumentDispatchBufferSize.getValue()) { |
| passManager.addPass(IREE::HAL::createMaterializeDispatchInstrumentationPass( |
| {bufferSize.value})); |
| } |
| |
| // Each executable needs a hal.interface to specify how the host and |
| // device communicate across the ABI boundary. |
| passManager.addPass(IREE::HAL::createMaterializeInterfacesPass()); |
| |
| // Prune unused executables and their contents. |
| passManager.addPass(IREE::HAL::createPruneExecutablesPass()); |
| |
| // Dump a source listing of each hal.executable and update the source |
| // locations in the IR. This will allow us to easily inspect each executable |
| // and give downstream tools that can display source information something |
| // more useful and slim than the entire original source model. |
| if (!targetOptions.executableSourcesPath.empty()) { |
| passManager.addPass(IREE::HAL::createDumpExecutableSourcesPass( |
| {targetOptions.executableSourcesPath})); |
| } |
| |
| // Substitute hal.executables we've generated from earlier phases of |
| // compilation with those specified on the command line. This developer |
| // feature allows for splicing in hand-authored or hand-modified executables |
| // in various forms without modifying the end-to-end compiler. Note that we do |
| // this prior to dumping benchmarks in order to allow generating new |
| // benchmarks using the substituted executables. |
| addExecutableSubstitutionPasses(passManager, clSubstituteExecutableSource, |
| clSubstituteExecutableSourcesFrom); |
| |
| // If debug information is requested capture the MLIR source text of each |
| // executable variant and associate it with the entry points. This allows us |
| // to preserve this information after translation and the original input IR |
| // has been erased. |
| if (targetOptions.debugLevel >= 3) { |
| passManager.addPass( |
| IREE::HAL::createCaptureExecutableSourcesPass({"0.source"})); |
| } |
| } |
| |
| //===----------------------------------------------------------------------===// |
| // --iree-hal-transformation-pipeline |
| //===----------------------------------------------------------------------===// |
| |
| void buildHALTransformPassPipeline(OpPassManager &passManager, |
| const TargetRegistry &targetRegistry, |
| const TargetOptions &targetOptions, |
| const TransformOptions &transformOptions, |
| PipelineHooks hooks, |
| PipelinePhase compileFrom, |
| PipelinePhase compileTo) { |
| //---------------------------------------------------------------------------- |
| // Device assignment and interface materialization |
| //---------------------------------------------------------------------------- |
| |
| if (hooks.beforePhase) { |
| hooks.beforePhase(PipelinePhase::ExecutableSources, passManager); |
| } |
| |
| if (compileFrom < PipelinePhase::ExecutableSources) { |
| AssignmentOptions assignmentOptions; |
| assignmentOptions.legacyTargetBackends = targetOptions.legacyTargetBackends; |
| assignmentOptions.targetDevices = targetOptions.targetDevices; |
| assignmentOptions.defaultDevice = targetOptions.defaultDevice; |
| buildHALDeviceAssignmentPassPipeline(passManager, targetRegistry, |
| assignmentOptions); |
| buildHALConfigurationPassPipeline(passManager, targetRegistry, |
| targetOptions, hooks); |
| |
| // Preprocess executables using an external tool. The tool may mutate one or |
| // more variants and even insert or remove variants. |
| for (auto command : clPreprocessExecutablesWith) { |
| passManager.addNestedPass<IREE::HAL::ExecutableOp>( |
| IREE::HAL::createPreprocessExecutablesPass(command)); |
| } |
| } |
| |
| if (hooks.afterPhase) { |
| hooks.afterPhase(PipelinePhase::ExecutableSources, passManager); |
| } |
| if (compileTo == PipelinePhase::ExecutableSources) { |
| return; |
| } |
| |
| //---------------------------------------------------------------------------- |
| // Executable translation |
| //---------------------------------------------------------------------------- |
| |
| if (hooks.beforePhase) { |
| hooks.beforePhase(PipelinePhase::ExecutableConfigurations, passManager); |
| } |
| |
| if (compileFrom < PipelinePhase::ExecutableConfigurations) { |
| // Select a translation strategy for each hal.executable.variant and |
| // generate the IR to condition on support for the variant. In the future, |
| // this or neighboring passes can expand/contract variants based on the |
| // selected translation strategies and the features each translation |
| // strategy are known to require or not require. |
| passManager.addNestedPass<IREE::HAL::ExecutableOp>( |
| IREE::HAL::createConfigureExecutablesPass({targetRegistry})); |
| |
| // Dump a second listing of each hal.executable after preprocessing and |
| // configuration of executables, as well as update locations in the IR. |
| if (!targetOptions.executableConfigurationsPath.empty()) { |
| passManager.addPass(IREE::HAL::createDumpExecutableSourcesPass( |
| {targetOptions.executableConfigurationsPath, "configured"})); |
| } |
| |
| // If debug information is requested capture the MLIR source text of each |
| // configured executable variant and associate it with the entry points. |
| if (targetOptions.debugLevel >= 3) { |
| passManager.addPass( |
| IREE::HAL::createCaptureExecutableSourcesPass({"1.configured"})); |
| } |
| |
| // Substitute hal.executables we've configured with those specified on the |
| // command line. This developer feature allows for hand editing the |
| // configured executable with different lowering parameters. |
| addExecutableSubstitutionPasses(passManager, |
| clSubstituteExecutableConfiguration, |
| clSubstituteExecutableConfigurationsFrom); |
| |
| // Dump standalone hal.executable benchmark modules. |
| // Today this only works for executables that have static dispatch |
| // parameters and is only useful for basic microbenchmarking. We do this |
| // after configuration to make it easy to tweak configurations directly |
| // from the benchmark. |
| if (!targetOptions.executableBenchmarksPath.empty()) { |
| passManager.addPass(IREE::HAL::createDumpExecutableBenchmarksPass( |
| {targetOptions.executableBenchmarksPath})); |
| } |
| } |
| |
| if (hooks.afterPhase) { |
| hooks.afterPhase(PipelinePhase::ExecutableConfigurations, passManager); |
| } |
| if (compileTo == PipelinePhase::ExecutableConfigurations) { |
| return; |
| } |
| |
| // TODO(benvanik): move translation after conversion; today translation |
| // inserts the workgroup count logic we need to convert but we could instead |
| // insert placeholder ops that are expanded after translation. |
| // |
| // Translate each executable variant to its target IR form. |
| // It's extremely important this runs parallelized as it's where a large |
| // majority of our compilation time lives (we invoke LLVM and lld and such). |
| // |
| // After this point the executables are opaque blobs and we cannot change |
| // their interfaces. |
| |
| if (hooks.beforePhase) { |
| hooks.beforePhase(PipelinePhase::ExecutableTargets, passManager); |
| } |
| |
| if (compileFrom < PipelinePhase::ExecutableTargets) { |
| passManager.addNestedPass<IREE::HAL::ExecutableOp>( |
| IREE::HAL::createTranslateAllExecutablesPass({targetRegistry})); |
| } |
| |
| // If debug information is requested capture the translated MLIR source text |
| // of each executable variant and associate it with the entry points. This |
| // allows us to compare the input IR with the translated IR before |
| // serialization (LLVM dialect, SPIR-V dialect, etc). |
| if (targetOptions.debugLevel >= 3) { |
| passManager.addPass( |
| IREE::HAL::createCaptureExecutableSourcesPass({"2.translated"})); |
| } |
| |
| if (hooks.afterPhase) { |
| hooks.afterPhase(PipelinePhase::ExecutableTargets, passManager); |
| } |
| if (compileTo == PipelinePhase::ExecutableTargets) { |
| return; |
| } |
| |
| // Substitute hal.executables we've translated with those specified on the |
| // command line. This developer feature allows for splicing in hand-authored |
| // or hand-modified executables in various forms without modifying the |
| // end-to-end compiler. We support substituting prior to translation as well |
| // but sometimes translation is required to produce the host code required |
| // for specialization and workgroup counts and we need to perform the |
| // substitution later. |
| addExecutableSubstitutionPasses(passManager, clSubstituteExecutableObject, |
| clSubstituteExecutableObjectsFrom); |
| |
| //---------------------------------------------------------------------------- |
| // Host program conversion |
| //---------------------------------------------------------------------------- |
| |
| // Convert supported input dialects (std, stream, etc) into the HAL dialect. |
| passManager.addPass(IREE::HAL::createConvertToHALPass()); |
| |
| // If memoization is disabled then inline any regions that were created during |
| // conversion. |
| if (!clMemoization) { |
| FunctionLikeNest(passManager) |
| .addPass(IREE::HAL::createInlineMemoizeRegionsPass); |
| } else { |
| passManager.addPass(IREE::HAL::createOutlineMemoizeRegionsPass()); |
| } |
| |
| // If any devices require the legacy synchronous execution behavior then |
| // make all async operations blocking. |
| passManager.addPass(IREE::HAL::createFixupLegacySyncPass()); |
| |
| // Prune unused executables and their contents. |
| passManager.addPass(IREE::HAL::createPruneExecutablesPass()); |
| |
| addCleanupPatterns(passManager); |
| |
| //---------------------------------------------------------------------------- |
| // Executable packing and runtime loading |
| //---------------------------------------------------------------------------- |
| |
| // TODO(benvanik): move translation down to here. |
| |
| // After all executables are translated and before resolving export |
| // ordinals we allow the backends to link executables together. For |
| // example, the LLVM AOT backend may combine all executable targets for the |
| // same architecture into a single executable and link it as a shared |
| // library. |
| if (transformOptions.linkExecutables && clLinkExecutables) { |
| passManager.addPass( |
| IREE::HAL::createLinkAllExecutablesPass({targetRegistry})); |
| } |
| |
| // If any executable variants have external objects referenced within them |
| // we hoist them up to the top-level variant. This is done after linking so |
| // that we have the greatest chance of combining executables without different |
| // object attrs preventing the merging. |
| passManager.nest<IREE::HAL::ExecutableOp>() |
| .addNestedPass<IREE::HAL::ExecutableVariantOp>( |
| IREE::HAL::createHoistExecutableObjectsPass()); |
| |
| // Resolve export ordinals from nested symbol references prior to |
| // serialization. As this pass creates lookup ops it should run before |
| // MaterializeResourceCachesPass. |
| passManager.addPass(IREE::HAL::createResolveExportOrdinalsPass()); |
| |
| // Gather cacheable resources such as executables and descriptor sets and |
| // cache them at initialization-time. |
| passManager.addPass(IREE::HAL::createMaterializeResourceCachesPass()); |
| |
| //---------------------------------------------------------------------------- |
| // Device management and specialization |
| //---------------------------------------------------------------------------- |
| |
| // Memoize device queries such that we don't need to repeatedly ask the same |
| // information at runtime. |
| passManager.addPass(IREE::HAL::createMemoizeDeviceQueriesPass()); |
| |
| // Big cleanup after all our conversion and materialization. |
| addCleanupPatterns(passManager); |
| |
| // Benchmarking only: repeat dispatch ops a certain number of times. |
| // This is guaranteed to invalidate program output and may introduce crashes |
| // if there are in-place dispatches that expect specific input data. |
| if (clBenchmarkDispatchRepeatCount != 1) { |
| FunctionLikeNest(passManager).addPass([&]() { |
| return IREE::HAL::createRepeatDispatchesPass( |
| {clBenchmarkDispatchRepeatCount}); |
| }); |
| } |
| |
| // Elide redundant command buffer state ops created during conversion. |
| FunctionLikeNest(passManager) |
| .addPass(IREE::HAL::createElideRedundantCommandsPass); |
| |
| // Initialize device globals now that we've done the analysis that is easier |
| // with them in their original target specification. |
| passManager.addPass(IREE::HAL::createInitializeDevicesPass({targetRegistry})); |
| |
| // TODO: Maybe this should be a part of Affine lowering pass. |
| // Remove if it is added there. |
| // https://github.com/llvm/llvm-project/issues/78458 |
| passManager.addPass(affine::createAffineExpandIndexOpsPass()); |
| // Fixup workgroup count calculations that may have used the affine dialect. |
| // Kind of random here but can happen if the benchmarking code does things. |
| passManager.addPass(mlir::createLowerAffinePass()); |
| |
| // TODO(benvanik): remove the need for this; some cleanup passes such as |
| // SimplifyGlobalAccesses are currently broken with scf present. |
| FunctionLikeNest(passManager).addPass(mlir::createConvertSCFToCFPass); |
| |
| //---------------------------------------------------------------------------- |
| // Executable serialization |
| //---------------------------------------------------------------------------- |
| |
| // Happens at the very end as IR is much more debuggable with the executable |
| // contents not turned into a big base64 string. |
| if (transformOptions.serializeExecutables) { |
| passManager.addNestedPass<IREE::HAL::ExecutableOp>( |
| IREE::HAL::createSerializeAllExecutablesPass( |
| {&targetRegistry, targetOptions.debugLevel, |
| targetOptions.executableIntermediatesPath, |
| targetOptions.executableBinariesPath})); |
| |
| // NOTE: symbol DCE will destroy executable target contents, so only run |
| // it if we serialized things. |
| passManager.addPass(IREE::HAL::createPruneExecutablesPass()); |
| passManager.addPass(mlir::createSymbolDCEPass()); |
| } |
| |
| //---------------------------------------------------------------------------- |
| // Whole-program optimization |
| //---------------------------------------------------------------------------- |
| |
| { |
| // We run these under a fixed-point iteration such that we can perform |
| // inter-procedural, intra-procedural, and canonicalization as separably |
| // verifiable/reusable passes. IPO will fold duplicate arguments/results |
| // and inline constants to allow the local optimizations to work more |
| // effectively. |
| OpPassManager ipoPipeline(mlir::ModuleOp::getOperationName()); |
| |
| // IPO and other cleanups. |
| addCleanupPatterns(ipoPipeline); |
| |
| // Large IPO pass. Note that this can introduce a significant amount of |
| // duplication/inlined constants and we'll want to ensure we're running |
| // cleanup again after (this entire set of patterns is run in a |
| // fixed-point iteration to do that). |
| ipoPipeline.addPass(IREE::Util::createIPOPass()); |
| |
| // Run fixed-point iteration on the IPO pipeline. |
| passManager.addPass( |
| IREE::Util::createFixedPointIteratorPass(std::move(ipoPipeline))); |
| } |
| } |
| |
| void buildHALTransformPassPipeline(OpPassManager &passManager, |
| const TargetRegistry &targetRegistry, |
| const TargetOptions &targetOptions, |
| PipelineHooks hooks, |
| PipelinePhase compileFrom, |
| PipelinePhase compileTo) { |
| TransformOptions transformOptions; |
| buildHALTransformPassPipeline(passManager, targetRegistry, targetOptions, |
| transformOptions, hooks, compileFrom, |
| compileTo); |
| } |
| |
| //===----------------------------------------------------------------------===// |
| // Registration |
| //===----------------------------------------------------------------------===// |
| |
| namespace { |
| #define GEN_PASS_REGISTRATION |
| #include "iree/compiler/Dialect/HAL/Transforms/Passes.h.inc" // IWYU pragma: export |
| } // namespace |
| |
| void registerHALPasses() { |
| // Force the flags to be bound. |
| // TODO(benvanik): remove the global flags and only rely on pipeline flags. |
| (void)IREE::HAL::TargetOptions::FromFlags::get(); |
| // TODO(multi-device): move the local device registration somewhere more |
| // centralized. For now we piggy-back on the pass registration as that's where |
| // the local device is used. |
| (void)IREE::HAL::LocalDevice::Options::FromFlags::get(); |
| IREE::HAL::TargetDeviceList deviceList; |
| deviceList.add("local", [=]() { |
| return std::make_shared<LocalDevice>( |
| IREE::HAL::LocalDevice::Options::FromFlags::get()); |
| }); |
| IREE::HAL::TargetRegistry::getMutableTargetRegistry().mergeFrom(deviceList); |
| |
| // Generated. |
| registerPasses(); |
| |
| // Pipelines. |
| PassPipelineRegistration<AssignmentOptions>( |
| "iree-hal-device-assignment-pipeline", |
| "Runs HAL target device assignment pipeline.", |
| [](OpPassManager &passManager, |
| const AssignmentOptions &assignmentOptions) { |
| buildHALDeviceAssignmentPassPipeline( |
| passManager, TargetRegistry::getGlobal(), assignmentOptions); |
| }); |
| PassPipelineRegistration<>("iree-hal-configuration-pipeline", |
| "Runs HAL target configuration pipeline.", |
| [](OpPassManager &passManager) { |
| buildHALConfigurationPassPipeline( |
| passManager, TargetRegistry::getGlobal(), |
| TargetOptions::FromFlags::get()); |
| }); |
| PassPipelineRegistration<TransformOptions>( |
| "iree-hal-transformation-pipeline", |
| "Runs the full IREE HAL conversion/lowering pipeline.", |
| [](OpPassManager &passManager, const TransformOptions &transformOptions) { |
| buildHALTransformPassPipeline(passManager, TargetRegistry::getGlobal(), |
| TargetOptions::FromFlags::get(), |
| transformOptions, PipelineHooks{}, |
| PipelinePhase::Start, PipelinePhase::End); |
| }); |
| } |
| |
| } // namespace mlir::iree_compiler::IREE::HAL |