Skip to content

Conversation

IanWood1
Copy link
Contributor

@IanWood1 IanWood1 commented Sep 11, 2025

Ops with the IsIsolatedFromAbove trait should be captured by the backward slice.

Signed-off-by: Ian Wood <ianwood@u.northwestern.edu>
@llvmbot llvmbot added the mlir label Sep 11, 2025
@llvmbot
Copy link
Member

llvmbot commented Sep 11, 2025

@llvm/pr-subscribers-mlir

Author: Ian Wood (IanWood1)

Changes

Ops with the IsIsolatedFromAbove trait can still have operands that would be useful to capture in the backwards slice.


Full diff: https://github.com/llvm/llvm-project/pull/158135.diff

1 Files Affected:

  • (modified) mlir/lib/Analysis/SliceAnalysis.cpp (+1-1)
diff --git a/mlir/lib/Analysis/SliceAnalysis.cpp b/mlir/lib/Analysis/SliceAnalysis.cpp
index 7037fa644c7be..d0e10626589ce 100644
--- a/mlir/lib/Analysis/SliceAnalysis.cpp
+++ b/mlir/lib/Analysis/SliceAnalysis.cpp
@@ -109,7 +109,7 @@ static LogicalResult getBackwardSliceImpl(Operation *op,
                                           DenseSet<Operation *> &visited,
                                           SetVector<Operation *> *backwardSlice,
                                           const BackwardSliceOptions &options) {
-  if (!op || op->hasTrait<OpTrait::IsIsolatedFromAbove>())
+  if (!op)
     return success();
 
   // Evaluate whether we should keep this def.

@IanWood1
Copy link
Contributor Author

Tests are here https://github.com/llvm/llvm-project/blob/6ab2b8745156269024de9098a4a6495ef19d546e/mlir/test/Transforms/move-operation-deps.mlir but I don't know of a good op to use in a testcase. I don't think its possible to give unregistered ops traits.

@hanhanW
Copy link
Contributor

hanhanW commented Sep 11, 2025

Are you able to use some Test ops? E.g.,

def IsolatedOneRegionOp : TEST_Op<"isolated_one_region_op", [IsolatedFromAbove]> {
let arguments = (ins Variadic<AnyType>:$operands);
let regions = (region AnyRegion:$my_region);
let assemblyFormat = [{
attr-dict-with-keyword $operands $my_region `:` type($operands)
}];
}

Copy link
Contributor

@MaheshRavishankar MaheshRavishankar left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This does make sense overall to me. Will probably need a test though....

Copy link
Member

@ftynse ftynse left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was intended as a recursion stopping condition. When getBackwardSliceImpl hits an isolated-from-above op, it doesn't need to go any further because there cannot be any values in the backward slice due to isolation. I see that this unintentionally prevents this from computing the slice of an operation that is itself isolated from above but may have operands. We shouldn't remove the original behavior though, but move the check to happen before recursive calls and not enter the recursion instead.

@IanWood1
Copy link
Contributor Author

When getBackwardSliceImpl hits an isolated-from-above op, it doesn't need to go any further because there cannot be any values in the backward slice due to isolation.

If it is isolated we should still add it to the slice and traverse the operands. However, the trait tells us that the op's regions don't access any values outside. So, do you mean the check should be moved here:

if (!options.omitUsesFromAbove) {

Signed-off-by: Ian Wood <ianwood@u.northwestern.edu>
@ftynse
Copy link
Member

ftynse commented Sep 17, 2025

I was thinking here

return getBackwardSliceImpl(parentOp, visited, backwardSlice,
(but not here
return getBackwardSliceImpl(definingOp, visited, backwardSlice,
IIUC) so we don't recurse into this function for an isolated-from-above parent of the current, but we include the isolated-from-above defining op and continue to its operands.

Incidentally, we shouldn't be recursing on the use-def chain because those tend to be long and stack tends to be small, but that is a follow-up.

Signed-off-by: Ian Wood <ianwood@u.northwestern.edu>
@IanWood1
Copy link
Contributor Author

IanWood1 commented Sep 17, 2025

Does isolated from above carry any other meaning than the ops regions don't use values outside of the region?

For example if you assume %2 is NOT isolated from above and you take a backwards slice from %2 it would contain {%2, %1, %0}. But I think you are suggesting that if it were isolated from above it should just contain {%2}. I'm not sure what about the IsolatedFromAbove trait causes this difference.

%0 = op
%1 = op_with_region %0 {
^bb0(%in):
   %2 = op_in_body %in
}

I think that it would make sense to include all 3 ops even if it %1 is IsolatedFromAbove. But also, I think this could be controlled by the user with options.filter.

@MaheshRavishankar
Copy link
Contributor

I think the issue is this https://github.com/llvm/llvm-project/pull/158135/files#diff-6e5b7f6ec0083b327b4c9960d81df86b74c89d2b51d91784d6d979c0c6031c78R200 .

Because of that, the operation could be either
(a) hit during traversing SSA use-def chains
(b) hit when you hit a basic block argument and then you "up-level" to the owner operation.

Maybe we just check for isIsolatedFromAbove right here : https://github.com/llvm/llvm-project/pull/158135/files#diff-6e5b7f6ec0083b327b4c9960d81df86b74c89d2b51d91784d6d979c0c6031c78R200

@joker-eph
Copy link
Collaborator

Does isolated from above carry any other meaning than the ops regions don't use values outside of the region?

For example if you assume %2 is NOT isolated from above and you take a backwards slice from %2 it would contain {%2, %1, %0}. But I think you are suggesting that if it were isolated from above it should just contain {%2}. I'm not sure what about the IsolatedFromAbove trait causes this difference.

%0 = op
%1 = op_with_region %0 {
^bb0(%in):
   %2 = op_in_body %in
}

I think that it would make sense to include all 3 ops even if it %1 is IsolatedFromAbove.

This is a correct analysis of MLIR semantics: whether it is a block argument of implicitly captured shouldn't make a difference.

@ftynse
Copy link
Member

ftynse commented Sep 17, 2025

Does isolated from above carry any other meaning than the ops regions don't use values outside of the region?

Strictly speaking, it does not. Now, the interpretation of that for the purpose of backward slice may differ. The interpretation adopted so far is that since values from outside the region cannot be used inside the region, they cannot directly affect any value/operation inside the region, and therefore should not be included in the slice. This is based on the IsolatedFromAbove initially being a generalization of function-like operations to gpu.launch, where one should avoid accessing values outside the region at all cost since they physically live on a different device. We can certainly do a post-hoc filtering, but why should we if we can just not collect them anyway.

This PR seems to adopt a different interpretation and try to account for a potential forwarding of the operands of the operation with the IsoaltedFromAbove trait and how they can be forwarded to block arguments. Handling this generally will likely require going through RegionBranchOpInterface to query which operands are forwarded to which blocks, though one could make a naive assumption that all operands should be included. At this point, just run a dataflow analysis instead.

For example if you assume %2 is NOT isolated from above and you take a backwards slice from %2 it would contain {%2, %1, %0}. But I think you are suggesting that if it were isolated from above it should just contain {%2}. I'm not sure what about the IsolatedFromAbove trait causes this difference.

%0 = op
%1 = op_with_region %0 {
^bb0(%in):
  %2 = op_in_body %in
}

%1 should not be included in the slice in any case, it is the result of op_with_region it does not exist at a point where op_in_body is executed. Both should also contain %in. Whether %0 is included depends on adopting one or another interpretation above. I'd suggest making this a flag, similar to ignoring all block arguments, as we have code in the ecosystem that depends on this behavior.

Because of that, the operation could be either
(a) hit during traversing SSA use-def chains
(b) hit when you hit a basic block argument and then you "up-level" to the owner operation.

Exactly. What I suggest is to at least parameterize the behavior of (b). I'd be also curious to see the practical use case this comes from, e.g., an isolated operation where we want to see isolated values in the slice somehow. What are you doing with them? I'd just take the slice of the isolated operation...

Maybe we just check for isIsolatedFromAbove right here : https://github.com/llvm/llvm-project/pull/158135/files#diff-6e5b7f6ec0083b327b4c9960d81df86b74c89d2b51d91784d6d979c0c6031c78R200

This code goes down, not up.

@ftynse
Copy link
Member

ftynse commented Sep 19, 2025

%1 should not be included in the slice in any case,

Or, rather, the operation that defines %1 may be in the slice, but not the value. I find it confusing to refer to operations by values they define in MLIR since operations are not values (unlike in LLVM IR).

@MaheshRavishankar
Copy link
Contributor

I agree with, lets "silo" the use of backward slice in the case of hitting block arguments, but I think Ian's fix here is the right one. When you encounter a block argument whether you hit it from within an operation that is isolated from above or not should not make a difference. So this PR seems fine as is.

Signed-off-by: Ian Wood <ianwood@u.northwestern.edu>
@IanWood1
Copy link
Contributor Author

I agree with, lets "silo" the use of backward slice in the case of hitting block arguments, but I think Ian's fix here is the right one. When you encounter a block argument whether you hit it from within an operation that is isolated from above or not should not make a difference. So this PR seems fine as is.

I think case about block arguments is a bit of a side-issue to my goal for this PR. I'm just trying to fix mlir::moveValueDefinitions which currently only allows traversing a single block. I made the change that @ftynse suggested since that maintains the current behavior with respect to traversing block arguments.

if (!options.omitUsesFromAbove) {
if (!options.omitUsesFromAbove &&
!op->hasTrait<OpTrait::IsIsolatedFromAbove>()) {
llvm::for_each(op->getRegions(), [&](Region &region) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note for reviewers: this line change is an NFC since if the op is isolated from above there can't be any uses defined above.

@IanWood1 IanWood1 merged commit 2dd3d38 into llvm:main Sep 22, 2025
9 checks passed
@llvm-ci
Copy link
Collaborator

llvm-ci commented Sep 22, 2025

LLVM Buildbot has detected a new failure on builder mlir-nvidia-gcc7 running on mlir-nvidia while building mlir at step 7 "test-build-check-mlir-build-only-check-mlir".

Full details are available at: https://lab.llvm.org/buildbot/#/builders/116/builds/18719

Here is the relevant piece of the build log for the reference
Step 7 (test-build-check-mlir-build-only-check-mlir) failure: test (failure)
******************** TEST 'MLIR :: Integration/GPU/CUDA/async.mlir' FAILED ********************
Exit Code: 1

Command Output (stdout):
--
# RUN: at line 1
/vol/worker/mlir-nvidia/mlir-nvidia-gcc7/llvm.obj/bin/mlir-opt /vol/worker/mlir-nvidia/mlir-nvidia-gcc7/llvm.src/mlir/test/Integration/GPU/CUDA/async.mlir  | /vol/worker/mlir-nvidia/mlir-nvidia-gcc7/llvm.obj/bin/mlir-opt -gpu-kernel-outlining  | /vol/worker/mlir-nvidia/mlir-nvidia-gcc7/llvm.obj/bin/mlir-opt -pass-pipeline='builtin.module(gpu.module(strip-debuginfo,convert-gpu-to-nvvm),nvvm-attach-target)'  | /vol/worker/mlir-nvidia/mlir-nvidia-gcc7/llvm.obj/bin/mlir-opt -gpu-async-region -gpu-to-llvm -reconcile-unrealized-casts -gpu-module-to-binary="format=fatbin"  | /vol/worker/mlir-nvidia/mlir-nvidia-gcc7/llvm.obj/bin/mlir-opt -async-to-async-runtime -async-runtime-ref-counting  | /vol/worker/mlir-nvidia/mlir-nvidia-gcc7/llvm.obj/bin/mlir-opt -convert-async-to-llvm -convert-func-to-llvm -convert-arith-to-llvm -convert-cf-to-llvm -reconcile-unrealized-casts  | /vol/worker/mlir-nvidia/mlir-nvidia-gcc7/llvm.obj/bin/mlir-runner    --shared-libs=/vol/worker/mlir-nvidia/mlir-nvidia-gcc7/llvm.obj/lib/libmlir_cuda_runtime.so    --shared-libs=/vol/worker/mlir-nvidia/mlir-nvidia-gcc7/llvm.obj/lib/libmlir_async_runtime.so    --shared-libs=/vol/worker/mlir-nvidia/mlir-nvidia-gcc7/llvm.obj/lib/libmlir_runner_utils.so    --entry-point-result=void -O0  | /vol/worker/mlir-nvidia/mlir-nvidia-gcc7/llvm.obj/bin/FileCheck /vol/worker/mlir-nvidia/mlir-nvidia-gcc7/llvm.src/mlir/test/Integration/GPU/CUDA/async.mlir
# executed command: /vol/worker/mlir-nvidia/mlir-nvidia-gcc7/llvm.obj/bin/mlir-opt /vol/worker/mlir-nvidia/mlir-nvidia-gcc7/llvm.src/mlir/test/Integration/GPU/CUDA/async.mlir
# executed command: /vol/worker/mlir-nvidia/mlir-nvidia-gcc7/llvm.obj/bin/mlir-opt -gpu-kernel-outlining
# executed command: /vol/worker/mlir-nvidia/mlir-nvidia-gcc7/llvm.obj/bin/mlir-opt '-pass-pipeline=builtin.module(gpu.module(strip-debuginfo,convert-gpu-to-nvvm),nvvm-attach-target)'
# executed command: /vol/worker/mlir-nvidia/mlir-nvidia-gcc7/llvm.obj/bin/mlir-opt -gpu-async-region -gpu-to-llvm -reconcile-unrealized-casts -gpu-module-to-binary=format=fatbin
# executed command: /vol/worker/mlir-nvidia/mlir-nvidia-gcc7/llvm.obj/bin/mlir-opt -async-to-async-runtime -async-runtime-ref-counting
# executed command: /vol/worker/mlir-nvidia/mlir-nvidia-gcc7/llvm.obj/bin/mlir-opt -convert-async-to-llvm -convert-func-to-llvm -convert-arith-to-llvm -convert-cf-to-llvm -reconcile-unrealized-casts
# executed command: /vol/worker/mlir-nvidia/mlir-nvidia-gcc7/llvm.obj/bin/mlir-runner --shared-libs=/vol/worker/mlir-nvidia/mlir-nvidia-gcc7/llvm.obj/lib/libmlir_cuda_runtime.so --shared-libs=/vol/worker/mlir-nvidia/mlir-nvidia-gcc7/llvm.obj/lib/libmlir_async_runtime.so --shared-libs=/vol/worker/mlir-nvidia/mlir-nvidia-gcc7/llvm.obj/lib/libmlir_runner_utils.so --entry-point-result=void -O0
# .---command stderr------------
# | 'cuStreamWaitEvent(stream, event, 0)' failed with 'CUDA_ERROR_CONTEXT_IS_DESTROYED'
# | 'cuEventDestroy(event)' failed with 'CUDA_ERROR_CONTEXT_IS_DESTROYED'
# | 'cuStreamWaitEvent(stream, event, 0)' failed with 'CUDA_ERROR_CONTEXT_IS_DESTROYED'
# | 'cuEventDestroy(event)' failed with 'CUDA_ERROR_CONTEXT_IS_DESTROYED'
# | 'cuStreamWaitEvent(stream, event, 0)' failed with 'CUDA_ERROR_CONTEXT_IS_DESTROYED'
# | 'cuStreamWaitEvent(stream, event, 0)' failed with 'CUDA_ERROR_CONTEXT_IS_DESTROYED'
# | 'cuEventDestroy(event)' failed with 'CUDA_ERROR_CONTEXT_IS_DESTROYED'
# | 'cuEventDestroy(event)' failed with 'CUDA_ERROR_CONTEXT_IS_DESTROYED'
# | 'cuEventSynchronize(event)' failed with 'CUDA_ERROR_CONTEXT_IS_DESTROYED'
# | 'cuEventDestroy(event)' failed with 'CUDA_ERROR_CONTEXT_IS_DESTROYED'
# `-----------------------------
# executed command: /vol/worker/mlir-nvidia/mlir-nvidia-gcc7/llvm.obj/bin/FileCheck /vol/worker/mlir-nvidia/mlir-nvidia-gcc7/llvm.src/mlir/test/Integration/GPU/CUDA/async.mlir
# .---command stderr------------
# | /vol/worker/mlir-nvidia/mlir-nvidia-gcc7/llvm.src/mlir/test/Integration/GPU/CUDA/async.mlir:68:12: error: CHECK: expected string not found in input
# |  // CHECK: [84, 84]
# |            ^
# | <stdin>:1:1: note: scanning from here
# | Unranked Memref base@ = 0x5be03602edf0 rank = 1 offset = 0 sizes = [2] strides = [1] data = 
# | ^
# | <stdin>:2:1: note: possible intended match here
# | [42, 42]
# | ^
# | 
# | Input file: <stdin>
# | Check file: /vol/worker/mlir-nvidia/mlir-nvidia-gcc7/llvm.src/mlir/test/Integration/GPU/CUDA/async.mlir
# | 
# | -dump-input=help explains the following input dump.
# | 
# | Input was:
# | <<<<<<
# |             1: Unranked Memref base@ = 0x5be03602edf0 rank = 1 offset = 0 sizes = [2] strides = [1] data =  
# | check:68'0     X~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ error: no match found
# |             2: [42, 42] 
# | check:68'0     ~~~~~~~~~
# | check:68'1     ?         possible intended match
...

IanWood1 added a commit to iree-org/iree that referenced this pull request Sep 24, 2025
#22088)

This can now be landed after
llvm/llvm-project#158135 has been integrated.
This was reverted due to #21889

Original PR #21894

Fixes #21889

Signed-off-by: Ian Wood <ianwood@u.northwestern.edu>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

7 participants