Adding compilation reentrancy tests and new HAL pipeline phases. (#12503)
Two new `--compile-to=` phases are supported:
- `executable-sources`: run just past interface materialization where
`hal.executable` ops with target configurations are present.
- `executable-targets`: run just past executable translation where
`hal.executable.variant` ops have been lowered to their final MLIR form
before linking and serialization (LLVM dialect, SPIR-V dialect, etc).
New tests are added that demonstrate and verify that iree-opt pipelines
can be run piecewise to produce a final output and that `--compile-to=`
at any particular phase can be passed back in and lowered down to a
final output.
A few passes were tweaked to ensure these tests pass.
diff --git a/compiler/src/iree/compiler/Dialect/HAL/Conversion/HALToVM/ConvertCommandBufferOps.cpp b/compiler/src/iree/compiler/Dialect/HAL/Conversion/HALToVM/ConvertCommandBufferOps.cpp
index f66bbfc..b68e096 100644
--- a/compiler/src/iree/compiler/Dialect/HAL/Conversion/HALToVM/ConvertCommandBufferOps.cpp
+++ b/compiler/src/iree/compiler/Dialect/HAL/Conversion/HALToVM/ConvertCommandBufferOps.cpp
@@ -256,11 +256,13 @@
}
return null;
};
+ auto i32Type = rewriter.getI32Type();
+ auto i64Type = rewriter.getI64Type();
SmallVector<Value, 8> callOperands = {
adaptor.getCommandBuffer(),
adaptor.getPipelineLayout(),
- adaptor.getSet(),
+ castToImportType(adaptor.getSet(), i32Type, rewriter),
};
SmallVector<int16_t, 5> segmentSizes = {
/*command_buffer=*/-1,
@@ -270,7 +272,8 @@
static_cast<int16_t>(adaptor.getBindingOrdinals().size()),
};
for (size_t i = 0; i < adaptor.getBindingOrdinals().size(); ++i) {
- callOperands.push_back(adaptor.getBindingOrdinals()[i]);
+ callOperands.push_back(
+ castToImportType(adaptor.getBindingOrdinals()[i], i32Type, rewriter));
auto bindingBuffer = adaptor.getBindingBuffers()[i];
if (bindingBuffer.getType().isa<IREE::VM::RefType>()) {
// Buffer binding; pass 0 for table slot.
@@ -281,10 +284,10 @@
callOperands.push_back(bindingBuffer);
callOperands.push_back(getNull());
}
- callOperands.push_back(castToImportType(adaptor.getBindingOffsets()[i],
- rewriter.getI64Type(), rewriter));
- callOperands.push_back(castToImportType(adaptor.getBindingLengths()[i],
- rewriter.getI64Type(), rewriter));
+ callOperands.push_back(
+ castToImportType(adaptor.getBindingOffsets()[i], i64Type, rewriter));
+ callOperands.push_back(
+ castToImportType(adaptor.getBindingLengths()[i], i64Type, rewriter));
}
auto callOp = rewriter.replaceOpWithNewOp<IREE::VM::CallVariadicOp>(
diff --git a/compiler/src/iree/compiler/Dialect/HAL/Transforms/MaterializeResourceCaches.cpp b/compiler/src/iree/compiler/Dialect/HAL/Transforms/MaterializeResourceCaches.cpp
index 5e485e9..6633df9 100644
--- a/compiler/src/iree/compiler/Dialect/HAL/Transforms/MaterializeResourceCaches.cpp
+++ b/compiler/src/iree/compiler/Dialect/HAL/Transforms/MaterializeResourceCaches.cpp
@@ -53,7 +53,33 @@
if (moduleOp.getBody()->empty()) return;
moduleBuilder = OpBuilder(&moduleOp.getBody()->front());
+ // Find all relevant ops. If we don't find any we skip the pass as it's
+ // likely it's already been run. We could fix the pass to better support
+ // partial materialization but there's no use cases for that today.
auto executableOps = llvm::to_vector<8>(moduleOp.getOps<ExecutableOp>());
+ SmallVector<IREE::HAL::DescriptorSetLayoutLookupOp>
+ descriptorSetLayoutLookupOps;
+ SmallVector<IREE::HAL::PipelineLayoutLookupOp> pipelineLayoutLookupOps;
+ SmallVector<IREE::HAL::ExecutableLookupOp> executableLookupOps;
+ for (Operation &funcLikeOp : moduleOp.getOps()) {
+ auto funcOp = llvm::dyn_cast<FunctionOpInterface>(funcLikeOp);
+ if (!funcOp) continue;
+ for (auto &block : funcOp.getFunctionBody()) {
+ block.walk([&](Operation *op) {
+ if (auto lookupOp = dyn_cast<DescriptorSetLayoutLookupOp>(op)) {
+ descriptorSetLayoutLookupOps.push_back(lookupOp);
+ } else if (auto lookupOp = dyn_cast<PipelineLayoutLookupOp>(op)) {
+ pipelineLayoutLookupOps.push_back(lookupOp);
+ } else if (auto lookupOp = dyn_cast<ExecutableLookupOp>(op)) {
+ executableLookupOps.push_back(lookupOp);
+ }
+ });
+ }
+ }
+ if (descriptorSetLayoutLookupOps.empty() &&
+ pipelineLayoutLookupOps.empty() && executableLookupOps.empty()) {
+ return;
+ }
// Declare all layouts used by the executables. This will ensure that the
// initialization order is correct as any pipeline layout needed (and its
@@ -73,28 +99,19 @@
// Declare executable variables so that we can reference them during lookup
// replacement.
for (auto executableOp : executableOps) {
- if (!defineExecutableOp(executableOp)) {
- signalPassFailure();
- return;
- }
+ defineExecutableOp(executableOp);
}
// Generate cached resource singletons and replace lookup ops with direct
// loads from variables.
- for (Operation &funcLikeOp : moduleOp.getOps()) {
- auto funcOp = llvm::dyn_cast<FunctionOpInterface>(funcLikeOp);
- if (!funcOp) continue;
- for (auto &block : funcOp.getFunctionBody()) {
- block.walk([&](Operation *op) {
- if (auto lookupOp = dyn_cast<DescriptorSetLayoutLookupOp>(op)) {
- replaceDescriptorSetLayoutLookupOp(lookupOp);
- } else if (auto lookupOp = dyn_cast<PipelineLayoutLookupOp>(op)) {
- replacePipelineLayoutLookupOp(lookupOp);
- } else if (auto lookupOp = dyn_cast<ExecutableLookupOp>(op)) {
- replaceExecutableLookupOp(lookupOp);
- }
- });
- }
+ for (auto lookupOp : descriptorSetLayoutLookupOps) {
+ replaceDescriptorSetLayoutLookupOp(lookupOp);
+ }
+ for (auto lookupOp : pipelineLayoutLookupOps) {
+ replacePipelineLayoutLookupOp(lookupOp);
+ }
+ for (auto lookupOp : executableLookupOps) {
+ replaceExecutableLookupOp(lookupOp);
}
}
@@ -182,7 +199,7 @@
return globalOp;
}
- IREE::Util::GlobalOp defineExecutableOp(ExecutableOp executableOp) {
+ void defineExecutableOp(ExecutableOp executableOp) {
auto loc = executableOp.getLoc();
auto symbolName =
(StringRef("_executable_") + executableOp.getSymName()).str();
@@ -259,8 +276,6 @@
blockBuilder.create<IREE::Util::GlobalStoreOp>(loc, executableValue,
globalOp.getName());
blockBuilder.create<IREE::Util::InitializerReturnOp>(loc);
-
- return globalOp;
}
// Inlines a constant block as a function in |moduleBuilder| and then inserts
diff --git a/compiler/src/iree/compiler/Dialect/HAL/Transforms/MemoizeDeviceQueries.cpp b/compiler/src/iree/compiler/Dialect/HAL/Transforms/MemoizeDeviceQueries.cpp
index 3f7daf8..5a474ff 100644
--- a/compiler/src/iree/compiler/Dialect/HAL/Transforms/MemoizeDeviceQueries.cpp
+++ b/compiler/src/iree/compiler/Dialect/HAL/Transforms/MemoizeDeviceQueries.cpp
@@ -64,6 +64,7 @@
}
// Create each query variable and replace the uses with loads.
+ SymbolTable symbolTable(moduleOp);
auto moduleBuilder = OpBuilder::atBlockBegin(moduleOp.getBody());
for (auto queryKey : llvm::enumerate(deviceQueryKeys)) {
auto queryOps = deviceQueryOps[queryKey.value()];
@@ -82,10 +83,12 @@
auto valueGlobalOp = moduleBuilder.create<IREE::Util::GlobalOp>(
fusedLoc, variableName,
/*isMutable=*/false, queryType);
+ symbolTable.insert(valueGlobalOp);
valueGlobalOp.setPrivate();
auto okGlobalOp = moduleBuilder.create<IREE::Util::GlobalOp>(
fusedLoc, variableName + "_ok",
/*isMutable=*/false, moduleBuilder.getI1Type());
+ symbolTable.insert(okGlobalOp);
okGlobalOp.setPrivate();
auto initializerOp =
diff --git a/compiler/src/iree/compiler/Dialect/HAL/Transforms/Passes.cpp b/compiler/src/iree/compiler/Dialect/HAL/Transforms/Passes.cpp
index 80c20d0..3b555f0 100644
--- a/compiler/src/iree/compiler/Dialect/HAL/Transforms/Passes.cpp
+++ b/compiler/src/iree/compiler/Dialect/HAL/Transforms/Passes.cpp
@@ -207,7 +207,8 @@
void buildHALTransformPassPipeline(OpPassManager &passManager,
const TargetOptions &targetOptions,
- const TransformOptions &transformOptions) {
+ const TransformOptions &transformOptions,
+ PipelinePhase compileTo) {
//----------------------------------------------------------------------------
// Device assignment and interface materialization
//----------------------------------------------------------------------------
@@ -225,6 +226,8 @@
createPreprocessExecutablesPass(command));
}
+ if (compileTo == PipelinePhase::ExecutableSources) 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.
@@ -238,6 +241,8 @@
passManager.addNestedPass<IREE::HAL::ExecutableOp>(
createTranslateExecutablesPass());
+ 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
@@ -370,9 +375,11 @@
}
void buildHALTransformPassPipeline(OpPassManager &passManager,
- const TargetOptions &targetOptions) {
+ const TargetOptions &targetOptions,
+ PipelinePhase compileTo) {
TransformOptions transformOptions;
- buildHALTransformPassPipeline(passManager, targetOptions, transformOptions);
+ buildHALTransformPassPipeline(passManager, targetOptions, transformOptions,
+ compileTo);
}
void registerHALConfigurationPassPipeline() {
@@ -390,8 +397,9 @@
"iree-hal-transformation-pipeline",
"Runs the full IREE HAL dialect transformation pipeline",
[](OpPassManager &passManager, const TransformOptions &transformOptions) {
- buildHALTransformPassPipeline(
- passManager, TargetOptions::FromFlags::get(), transformOptions);
+ buildHALTransformPassPipeline(passManager,
+ TargetOptions::FromFlags::get(),
+ transformOptions, PipelinePhase::End);
});
}
diff --git a/compiler/src/iree/compiler/Dialect/HAL/Transforms/Passes.h b/compiler/src/iree/compiler/Dialect/HAL/Transforms/Passes.h
index bcd949e..3c6cdf2 100644
--- a/compiler/src/iree/compiler/Dialect/HAL/Transforms/Passes.h
+++ b/compiler/src/iree/compiler/Dialect/HAL/Transforms/Passes.h
@@ -25,6 +25,15 @@
// Helpers
//===----------------------------------------------------------------------===//
+enum class PipelinePhase {
+ // Runs the transform pipeline up to executable sources (pre translation).
+ ExecutableSources,
+ // Runs the transform pipeline until just after executable translation.
+ ExecutableTargets,
+ // Runs the full pipeline.
+ End,
+};
+
// Adds a set of passes to the given pass manager that run the required HAL
// transforms in the canonical order.
//
@@ -35,8 +44,9 @@
// <run conversion to flow/sequencer/etc>
// buildHALTransformPassPipeline & run
// <run conversion from HAL to vm/etc>
-void buildHALTransformPassPipeline(OpPassManager &passManager,
- const TargetOptions &targetOptions);
+void buildHALTransformPassPipeline(
+ OpPassManager &passManager, const TargetOptions &targetOptions,
+ PipelinePhase compileTo = PipelinePhase::End);
// Adds a set of passes to the given pass manager that run the head of the HAL
// pipeline to assign devices, materialize interfaces, and translate
diff --git a/compiler/src/iree/compiler/Dialect/HAL/Transforms/test/materialize_resource_caches.mlir b/compiler/src/iree/compiler/Dialect/HAL/Transforms/test/materialize_resource_caches.mlir
index c11705d..dba9d91 100644
--- a/compiler/src/iree/compiler/Dialect/HAL/Transforms/test/materialize_resource_caches.mlir
+++ b/compiler/src/iree/compiler/Dialect/HAL/Transforms/test/materialize_resource_caches.mlir
@@ -208,3 +208,73 @@
}
}
+
+// -----
+
+// Tests that materialization no-ops when resource caches have already been
+// materialized. Today this is rather simplistic and just bails if the names
+// match with the expectation being that users are mostly just running through
+// with --compile-to=hal and not trying to mutate intermediate HAL state. We
+// could rework the pass to support only materializing what's required based on
+// what resources are looked up.
+
+#pipeline_layout_0 = #hal.pipeline.layout<push_constants = 0, sets = [
+ #hal.descriptor_set.layout<0, bindings = [
+ #hal.descriptor_set.binding<0, storage_buffer>,
+ #hal.descriptor_set.binding<1, storage_buffer>
+ ]>
+]>
+
+module attributes {hal.device.targets = [#hal.device.target<"llvm-cpu">]} {
+
+util.global private @_descriptor_set_layout_0 : !hal.descriptor_set_layout
+util.initializer {
+ %device = hal.ex.shared_device : !hal.device
+ %descriptor_set_layout = hal.descriptor_set_layout.create device(%device : !hal.device) flags("None") bindings([#hal.descriptor_set.binding<0, storage_buffer>, #hal.descriptor_set.binding<1, storage_buffer>]) : !hal.descriptor_set_layout
+ util.global.store %descriptor_set_layout, @_descriptor_set_layout_0 : !hal.descriptor_set_layout
+ util.initializer.return
+}
+
+util.global private @_pipeline_layout_0 : !hal.pipeline_layout
+util.initializer {
+ %_descriptor_set_layout_0 = util.global.load @_descriptor_set_layout_0 : !hal.descriptor_set_layout
+ %device = hal.ex.shared_device : !hal.device
+ %pipeline_layout = hal.pipeline_layout.create device(%device : !hal.device) push_constants(0) layouts([%_descriptor_set_layout_0]) : !hal.pipeline_layout
+ util.global.store %pipeline_layout, @_pipeline_layout_0 : !hal.pipeline_layout
+ util.initializer.return
+}
+
+util.global private @_executable_exe : !hal.executable
+util.initializer {
+ %device = hal.ex.shared_device : !hal.device
+ %0 = hal.device.switch<%device : !hal.device> -> !hal.executable
+ #hal.device.match.executable.format<"vmvx-bytecode-fb"> {
+ %_pipeline_layout_0 = util.global.load @_pipeline_layout_0 : !hal.pipeline_layout
+ %exe = hal.executable.create device(%device : !hal.device) target(@exe0::@vmvx) layouts([%_pipeline_layout_0]) : !hal.executable
+ hal.return %exe : !hal.executable
+ },
+ #hal.match.always {
+ %1 = util.null : !hal.executable
+ hal.return %1 : !hal.executable
+ }
+ util.global.store %0, @_executable_exe : !hal.executable
+ util.initializer.return
+}
+
+hal.executable @exe {
+ hal.executable.variant @vmvx, target = <"vmvx", "vmvx-bytecode-fb"> {
+ hal.executable.export @entry ordinal(0) layout(#pipeline_layout_0) attributes {
+ workgroup_size = [32 : index, 1 : index, 1 : index]
+ }
+ }
+}
+
+// CHECK-LABEL: @exeLookup
+func.func @exeLookup(%device : !hal.device) -> !hal.executable {
+ // CHECK: %[[EXE:.+]] = util.global.load @_executable_exe : !hal.executable
+ %0 = util.global.load @_executable_exe : !hal.executable
+ // CHECK-NEXT: return %[[EXE]]
+ return %0 : !hal.executable
+}
+
+}
diff --git a/compiler/src/iree/compiler/Dialect/Stream/Transforms/VerifyLowerings.cpp b/compiler/src/iree/compiler/Dialect/Stream/Transforms/VerifyLowerings.cpp
index 0902d82..b2816b5 100644
--- a/compiler/src/iree/compiler/Dialect/Stream/Transforms/VerifyLowerings.cpp
+++ b/compiler/src/iree/compiler/Dialect/Stream/Transforms/VerifyLowerings.cpp
@@ -303,11 +303,11 @@
void runOnOperation() override {
// We cannot have stream.cmd.* ops mixed with stream.tensor/async.* ops
- // as they use different memory models.
+ // as they use different memory models. We need to allow them through,
+ // though, to allow for compiler re-entrancy.
Verifier verifier;
setupDefaultOpLegality(verifier);
markTensorInputsIllegal(verifier);
- markStreamCmdOpsIllegal(verifier);
if (failed(verifier.run(getOperation()))) {
return signalPassFailure();
}
@@ -339,12 +339,12 @@
void runOnOperation() override {
// We cannot have stream.cmd.* ops mixed with stream.tensor/async.* ops
- // as they use different memory models.
+ // as they use different memory models. We need to allow them through,
+ // though, to allow for compiler re-entrancy.
Verifier verifier;
setupDefaultOpLegality(verifier);
markTensorInputsIllegal(verifier);
markStreamTensorOpsIllegal(verifier);
- markStreamCmdOpsIllegal(verifier);
// All resources should have had their usage assigned.
verifier.addTypeVerifier<IREE::Stream::ResourceType>([](auto type) {
@@ -357,6 +357,11 @@
// All streamable ops should be inside of execution regions.
verifier.addOpVerifier<IREE::Stream::StreamableOpInterface>(
[](auto op) -> Optional<Verifier::Legality> {
+ // Skip cmd ops that may exist.
+ if (op->template hasTrait<OpTrait::IREE::Stream::CmdPhaseOp>()) {
+ return Verifier::Legality::LEGAL;
+ }
+
// Allow metadata ops outside of execution regions.
if (op.isMetadata()) return Verifier::Legality::LEGAL;
diff --git a/compiler/src/iree/compiler/Dialect/Util/IR/UtilOps.td b/compiler/src/iree/compiler/Dialect/Util/IR/UtilOps.td
index 500f69c..9c79984 100644
--- a/compiler/src/iree/compiler/Dialect/Util/IR/UtilOps.td
+++ b/compiler/src/iree/compiler/Dialect/Util/IR/UtilOps.td
@@ -350,7 +350,11 @@
let hasCanonicalizer = 1;
}
-def Util_UnreachableOp : Util_Op<"unreachable", [NoMemoryEffect, Terminator]> {
+def Util_UnreachableOp : Util_Op<"unreachable", [
+ NoMemoryEffect,
+ ReturnLike,
+ Terminator
+ ]> {
let summary = [{unreachable assertion op}];
let description = [{
Signals to the compiler that the parent block should not be reachable.
diff --git a/compiler/src/iree/compiler/Pipelines/Pipelines.cpp b/compiler/src/iree/compiler/Pipelines/Pipelines.cpp
index 904f297..cb1c611 100644
--- a/compiler/src/iree/compiler/Pipelines/Pipelines.cpp
+++ b/compiler/src/iree/compiler/Pipelines/Pipelines.cpp
@@ -145,6 +145,19 @@
break;
}
+ IREE::HAL::PipelinePhase halCompileTo;
+ switch (compileTo) {
+ default:
+ halCompileTo = IREE::HAL::PipelinePhase::End;
+ break;
+ case IREEVMPipelinePhase::ExecutableSources:
+ halCompileTo = IREE::HAL::PipelinePhase::ExecutableSources;
+ break;
+ case IREEVMPipelinePhase::ExecutableTargets:
+ halCompileTo = IREE::HAL::PipelinePhase::ExecutableTargets;
+ break;
+ }
+
IREE_TRACE_ADD_BEGIN_FRAME_PASS(passManager, "HAL");
switch (schedulingOptions.executionModel) {
case SchedulingOptions::ExecutionModel::HostOnly:
@@ -153,7 +166,8 @@
default:
case SchedulingOptions::ExecutionModel::AsyncInternal:
case SchedulingOptions::ExecutionModel::AsyncExternal:
- IREE::HAL::buildHALTransformPassPipeline(passManager, executableOptions);
+ IREE::HAL::buildHALTransformPassPipeline(passManager, executableOptions,
+ halCompileTo);
break;
case SchedulingOptions::ExecutionModel::InlineStatic:
IREE::HAL::Inline::buildHALInlineStaticTransformPassPipeline(
@@ -165,7 +179,10 @@
break;
}
IREE_TRACE_ADD_END_FRAME_PASS(passManager, "HAL");
- if (compileTo == IREEVMPipelinePhase::HAL) return; // early-exit
+ if (compileTo == IREEVMPipelinePhase::HAL ||
+ halCompileTo != IREE::HAL::PipelinePhase::End) {
+ return; // early-exit
+ }
IREE_TRACE_ADD_BEGIN_FRAME_PASS(passManager, "VM");
IREE::VM::buildVMTransformPassPipeline(passManager, targetOptions);
diff --git a/compiler/src/iree/compiler/Pipelines/Pipelines.h b/compiler/src/iree/compiler/Pipelines/Pipelines.h
index c8781b2..c135f47 100644
--- a/compiler/src/iree/compiler/Pipelines/Pipelines.h
+++ b/compiler/src/iree/compiler/Pipelines/Pipelines.h
@@ -34,6 +34,8 @@
Preprocessing,
Flow,
Stream,
+ ExecutableSources,
+ ExecutableTargets,
HAL,
VM,
End,
@@ -54,6 +56,11 @@
"Compiles up to the `flow` dialect.");
callback(IREEVMPipelinePhase::Stream, "stream",
"Compiles up to the `stream` dialect.");
+ callback(IREEVMPipelinePhase::ExecutableSources, "executable-sources",
+ "Compiles up to just before `hal.executable`s are translated, "
+ "excluding codegen.");
+ callback(IREEVMPipelinePhase::ExecutableTargets, "executable-targets",
+ "Compiles up to translated `hal.executable`s, including codegen.");
callback(IREEVMPipelinePhase::HAL, "hal",
"Compiles up to the `hal` dialect, including codegen.");
callback(IREEVMPipelinePhase::VM, "vm", "Compiles up to the `vm` dialect.");
diff --git a/tools/test/BUILD b/tools/test/BUILD
index 8bd9fa6..f7bf4f8 100644
--- a/tools/test/BUILD
+++ b/tools/test/BUILD
@@ -18,6 +18,8 @@
name = "lit",
srcs = enforce_glob(
[
+ "compile_pipelines.mlir",
+ "compile_to_continuation.mlir",
"compile_to_phase.mlir",
"executable_benchmarks.mlir",
"executable_sources.mlir",
@@ -46,6 +48,7 @@
tools = [
"//tools:iree-benchmark-module",
"//tools:iree-compile",
+ "//tools:iree-opt",
"//tools:iree-run-mlir",
"//tools:iree-run-module",
"@llvm-project//lld",
diff --git a/tools/test/CMakeLists.txt b/tools/test/CMakeLists.txt
index 64a3e22..7c92d9d 100644
--- a/tools/test/CMakeLists.txt
+++ b/tools/test/CMakeLists.txt
@@ -14,6 +14,8 @@
NAME
lit
SRCS
+ "compile_pipelines.mlir"
+ "compile_to_continuation.mlir"
"compile_to_phase.mlir"
"executable_benchmarks.mlir"
"executable_sources.mlir"
@@ -32,6 +34,7 @@
FileCheck
iree-benchmark-module
iree-compile
+ iree-opt
iree-run-mlir
iree-run-module
not
diff --git a/tools/test/compile_pipelines.mlir b/tools/test/compile_pipelines.mlir
new file mode 100644
index 0000000..7275ec9
--- /dev/null
+++ b/tools/test/compile_pipelines.mlir
@@ -0,0 +1,14 @@
+// RUN: iree-opt --iree-common-input-transformation-pipeline %s | \
+// RUN: iree-opt --iree-abi-transformation-pipeline - | \
+// RUN: iree-opt --iree-common-input-transformation-pipeline - | \
+// RUN: iree-opt --iree-flow-transformation-pipeline - | \
+// RUN: iree-opt --iree-stream-transformation-pipeline - | \
+// RUN: iree-opt --iree-hal-transformation-pipeline --iree-hal-target-backends=vmvx - | \
+// RUN: iree-opt --iree-vm-transformation-pipeline - | \
+// RUN: FileCheck %s
+
+// CHECK: vm.func private @abs(%arg0: !vm.ref<!hal.buffer_view>) -> !vm.ref<!hal.buffer_view>
+func.func @abs(%input : tensor<f32>) -> (tensor<f32>) {
+ %result = math.absf %input : tensor<f32>
+ return %result : tensor<f32>
+}
diff --git a/tools/test/compile_to_continuation.mlir b/tools/test/compile_to_continuation.mlir
new file mode 100644
index 0000000..631afca
--- /dev/null
+++ b/tools/test/compile_to_continuation.mlir
@@ -0,0 +1,44 @@
+// RUN: iree-compile --compile-to=input %s | \
+// RUN: iree-compile --output-format=vm-asm --iree-hal-target-backends=vmvx - | \
+// RUN: FileCheck %s --check-prefix=INPUT-PHASE
+// INPUT-PHASE: vm.func private @abs(%arg0: !vm.ref<!hal.buffer_view>) -> !vm.ref<!hal.buffer_view>
+
+// RUN: iree-compile --compile-to=abi %s | \
+// RUN: iree-compile --output-format=vm-asm --iree-hal-target-backends=vmvx - | \
+// RUN: FileCheck %s --check-prefix=ABI-PHASE
+// ABI-PHASE: vm.func private @abs(%arg0: !vm.ref<!hal.buffer_view>) -> !vm.ref<!hal.buffer_view>
+
+// RUN: iree-compile --compile-to=flow %s | \
+// RUN: iree-compile --output-format=vm-asm --iree-hal-target-backends=vmvx - | \
+// RUN: FileCheck %s --check-prefix=FLOW-PHASE
+// FLOW-PHASE: vm.func private @abs(%arg0: !vm.ref<!hal.buffer_view>) -> !vm.ref<!hal.buffer_view>
+
+// RUN: iree-compile --compile-to=stream %s | \
+// RUN: iree-compile --output-format=vm-asm --iree-hal-target-backends=vmvx - | \
+// RUN: FileCheck %s --check-prefix=STREAM-PHASE
+// STREAM-PHASE: vm.func private @abs(%arg0: !vm.ref<!hal.buffer_view>) -> !vm.ref<!hal.buffer_view>
+
+// RUN: iree-compile --compile-to=executable-sources --iree-hal-target-backends=vmvx %s | \
+// RUN: iree-compile --output-format=vm-asm - | \
+// RUN: FileCheck %s --check-prefix=EXECUTABLE-SOURCES-PHASE
+// EXECUTABLE-SOURCES-PHASE: vm.func private @abs(%arg0: !vm.ref<!hal.buffer_view>) -> !vm.ref<!hal.buffer_view>
+
+// RUN: iree-compile --compile-to=executable-targets --iree-hal-target-backends=vmvx %s | \
+// RUN: iree-compile --output-format=vm-asm - | \
+// RUN: FileCheck %s --check-prefix=EXECUTABLE-TARGETS-PHASE
+// EXECUTABLE-TARGETS-PHASE: vm.func private @abs(%arg0: !vm.ref<!hal.buffer_view>) -> !vm.ref<!hal.buffer_view>
+
+// RUN: iree-compile --compile-to=hal --iree-hal-target-backends=vmvx %s | \
+// RUN: iree-compile --output-format=vm-asm - | \
+// RUN: FileCheck %s --check-prefix=HAL-PHASE
+// HAL-PHASE: vm.func private @abs(%arg0: !vm.ref<!hal.buffer_view>) -> !vm.ref<!hal.buffer_view>
+
+// RUN: iree-compile --compile-to=vm --iree-hal-target-backends=vmvx %s | \
+// RUN: iree-compile --output-format=vm-asm - | \
+// RUN: FileCheck %s --check-prefix=VM-PHASE
+// VM-PHASE: vm.func private @abs(%arg0: !vm.ref<!hal.buffer_view>) -> !vm.ref<!hal.buffer_view>
+
+func.func @abs(%input : tensor<f32>) -> (tensor<f32>) {
+ %result = math.absf %input : tensor<f32>
+ return %result : tensor<f32>
+}
diff --git a/tools/test/compile_to_phase.mlir b/tools/test/compile_to_phase.mlir
index cb3a8ed..b88dcac 100644
--- a/tools/test/compile_to_phase.mlir
+++ b/tools/test/compile_to_phase.mlir
@@ -1,20 +1,31 @@
-// RUN: iree-compile --compile-to=input --iree-hal-target-backends=vmvx %s | FileCheck %s --check-prefix=INPUT-PHASE
+// RUN: iree-compile --compile-to=input %s | FileCheck %s --check-prefix=INPUT-PHASE
// INPUT-PHASE: func.func @abs(%[[ARG0:.+]]: tensor<f32>)
// INPUT-PHASE: math.absf %[[ARG0]] : tensor<f32>
-// RUN: iree-compile --compile-to=abi --iree-hal-target-backends=vmvx %s | FileCheck %s --check-prefix=ABI-PHASE
+// RUN: iree-compile --compile-to=abi %s | FileCheck %s --check-prefix=ABI-PHASE
// ABI-PHASE: func.func @abs(%[[ARG0:.+]]: !hal.buffer_view)
// ABI-PHASE: %[[INPUT:.+]] = hal.tensor.import %[[ARG0]] : !hal.buffer_view -> tensor<f32>
// ABI-PHASE: math.absf %[[INPUT]] : tensor<f32>
-// RUN: iree-compile --compile-to=flow --iree-hal-target-backends=vmvx %s | FileCheck %s --check-prefix=FLOW-PHASE
+// RUN: iree-compile --compile-to=flow %s | FileCheck %s --check-prefix=FLOW-PHASE
// FLOW-PHASE: flow.executable.export public @abs_dispatch_0
// FLOW-PHASE: flow.dispatch @abs_dispatch_0
-// RUN: iree-compile --compile-to=stream --iree-hal-target-backends=vmvx %s | FileCheck %s --check-prefix=STREAM-PHASE
+// RUN: iree-compile --compile-to=stream %s | FileCheck %s --check-prefix=STREAM-PHASE
// STREAM-PHASE: stream.executable.export public @abs_dispatch_0
// STREAM-PHASE: stream.cmd.dispatch @abs_dispatch_0
+// RUN: iree-compile --compile-to=executable-sources --iree-hal-target-backends=vmvx %s | FileCheck %s --check-prefix=EXECUTABLE-SOURCES-PHASE
+// EXECUTABLE-SOURCES-PHASE: hal.executable private @abs_dispatch_0
+// EXECUTABLE-SOURCES-PHASE: hal.executable.variant
+// EXECUTABLE-SOURCES-PHASE: linalg.generic
+// EXECUTABLE-SOURCES-PHASE: math.absf
+
+// RUN: iree-compile --compile-to=executable-targets --iree-hal-target-backends=vmvx %s | FileCheck %s --check-prefix=EXECUTABLE-TARGETS-PHASE
+// EXECUTABLE-TARGETS-PHASE: hal.executable private @abs_dispatch_0
+// EXECUTABLE-TARGETS-PHASE: hal.executable.variant
+// EXECUTABLE-TARGETS-PHASE: vm.abs.f32
+
// RUN: iree-compile --compile-to=hal --iree-hal-target-backends=vmvx %s | FileCheck %s --check-prefix=HAL-PHASE
// HAL-PHASE: hal.executable private @abs_dispatch_0
// HAL-PHASE: hal.executable.binary