summaryrefslogtreecommitdiff
path: root/mlir/lib/Analysis/DataFlow/LivenessAnalysis.cpp
AgeCommit message (Collapse)Author
2025-10-21[mlir][dataflow] Fix LivenessAnalysis/RemoveDeadValues handling of loop ↵lonely eagle
induction variables (#161117) Fix https://github.com/llvm/llvm-project/issues/157934. In liveness analysis, variables that are not analyzed are set as dead variables, but some variables are definitely live. --------- Co-authored-by: Mehdi Amini <joker.eph@gmail.com>
2025-09-28[mlir][dataflow] Use skipRegions to print region op (NFC) (#161066)lonely eagle
The print region op prints a lot of useless IR. Use OpWithFlags(op, OpPrintingFlags().skipRegions()) to avoid this.
2025-09-26[MLIR] Fix LivenessAnalysis/RemoveDeadValues handling of dead function ↵Mehdi Amini
arguments (#160755) In #153973 I added the correctly handling of block arguments, unfortunately this was gated on operation that also have results. This wasn't intentional and this excluded operations like function from being correctly processed.
2025-08-18[MLIR] Fix Liveness analysis handling of unreachable code (#153973)Mehdi Amini
This patch is forcing all values to be initialized by the LivenessAnalysis, even in dead blocks. The dataflow framework will skip visiting values when its already knows that a block is dynamically unreachable, so this requires specific handling. Downstream code could consider that the absence of liveness is the same a "dead". However as the code is mutated, new value can be introduced, and a transformation like "RemoveDeadValue" must conservatively consider that the absence of liveness information meant that we weren't sure if a value was dead (it could be a newly introduced value. Fixes #153906
2025-07-27Migrate more of DataFlow framework to LDBG (NFC) (#150752)Mehdi Amini
2025-07-25[MLIR] Remove unused DBGS macro (NFC)Mehdi Amini
2025-07-25[MLIR] Add a OpWithFlags class that acts as a "stream modifier" to customize ↵Mehdi Amini
Operation streaming (#150636)
2025-07-25[mlir] Switch to new LDBG macro (#150616)Jacques Pienaar
Change local variants to use new central one.
2025-06-22[MLIR] Add logging/tracing to DataFlow analysis and RemoveDeadValues (NFC) ↵Mehdi Amini
(#144695) Debugging issues with this pass is quite difficult at the moment, this should help.
2025-06-20Define a DataFlowSolver helper that loads sensible default analyses (#143415)Jeremy Kun
Cf. https://discourse.llvm.org/t/mlir-dead-code-analysis/67568/10 Custom analysis passes will not work properly unless both DeadCodeAnalysis and SparseConstantPropagation are loaded to the DataFlowSolver. This is intended behavior, but surprising to many users as shown in the thread. In lieu of a longer-term fix (which I am not knowledgeable enough to implement myself, yet), this commit adds a helper function that loads these two analyses, as well as providing breadcrumbs for an explanation of the problem. The existing places in the codebase where these two analyses are loaded for the purpose of running other unrelated analyses are replaced by the use of the helper. --------- Co-authored-by: Jeremy Kun <j2kun@users.noreply.github.com> Co-authored-by: Oleksandr "Alex" Zinenko <azinenko@amd.com>
2025-05-20[mlir] [liveness] Conservatively mark operands of return-like op inside ↵Nhat Nguyen
non-callable and non-regionbranch op as live (#140793) Currently the liveness analysis always marks operands yielded in regions that aren't classified as `RegionBranchOpInterface` or `CallableOpInterface` as non-live. Examples for these ops include linalg.generic (with `linalg.yield` as terminator) or gpu ops (with `gpu.yield` as terminator). This in turn makes the `remove-dead-values` pass always incorrectly remove the bodies of these ops, leading to invalid IR. Because these ops define their own semantics, I have conservatively marked all operands of these yield ops to be live.
2025-04-02[mlir][liveness] fix bugs in liveness analysis (#133416)donald chen
This patch fixes the following bugs: - In SparseBackwardAnalysis, the setToExitState function should propagate changes if it modifies the lattice. Previously, this issue was masked because multi-block scenarios were not tested, and the traversal order of backward data flow analysis starts from the end of the program. - The method in liveness analysis for determining whether the non-forwarded operand in branch/region branch operations is live is incorrect, which may cause originally live variables to be marked as not live.
2024-10-11[mlir] [dataflow] unify semantics of program point (#110344)donald chen
The concept of a 'program point' in the original data flow framework is ambiguous. It can refer to either an operation or a block itself. This representation has different interpretations in forward and backward data-flow analysis. In forward data-flow analysis, the program point of an operation represents the state after the operation, while in backward data flow analysis, it represents the state before the operation. When using forward or backward data-flow analysis, it is crucial to carefully handle this distinction to ensure correctness. This patch refactors the definition of program point, unifying the interpretation of program points in both forward and backward data-flow analysis. How to integrate this patch? For dense forward data-flow analysis and other analysis (except dense backward data-flow analysis), the program point corresponding to the original operation can be obtained by `getProgramPointAfter(op)`, and the program point corresponding to the original block can be obtained by `getProgramPointBefore(block)`. For dense backward data-flow analysis, the program point corresponding to the original operation can be obtained by `getProgramPointBefore(op)`, and the program point corresponding to the original block can be obtained by `getProgramPointAfter(block)`. NOTE: If you need to get the lattice of other data-flow analyses in dense backward data-flow analysis, you should still use the dense forward data-flow approach. For example, to get the Executable state of a block in dense backward data-flow analysis and add the dependency of the current operation, you should write: ``getOrCreateFor<Executable>(getProgramPointBefore(op), getProgramPointBefore(block))`` In case above, we use getProgramPointBefore(op) because the analysis we rely on is dense backward data-flow, and we use getProgramPointBefore(block) because the lattice we query is the result of a non-dense backward data flow computation. related dsscussion: https://discourse.llvm.org/t/rfc-unify-the-semantics-of-program-points/80671/8 corresponding PSA: https://discourse.llvm.org/t/psa-program-point-semantics-change/81479
2024-08-22[mlir][dataflow] Propagate errors from `visitOperation` (#105448)Ivan Butygin
Base `DataFlowAnalysis::visit` returns `LogicalResult`, but wrappers's Sparse/Dense/Forward/Backward `visitOperation` doesn't. Sometimes it's needed to abort solver early if some unrecoverable condition detected inside analysis. Update `visitOperation` to return `LogicalResult` and propagate it to `solver.initializeAndRun()`. Only `visitOperation` is updated for now, it's possible to update other hooks like `visitNonControlFlowArguments`, bit it's not needed immediately and let's keep this PR small. Hijacked `UnderlyingValueAnalysis` test analysis to test it.
2023-12-21[mlir] mark ChangeResult as nodiscard (#76147)Oleksandr "Alex" Zinenko
This enum is used by dataflow analyses to indicate whether further propagation is necessary to reach the fix point. Accidentally discarding such a value will likely lead to propagation stopping early, leading to incomplete or incorrect results. The most egregious example is the duality between `join` on the analysis class, which triggers propagation internally, and `join` on the lattice class that does not and expects the caller to trigger it depending on the returned `ChangeResult`.
2023-10-28Apply clang-tidy fixes for misc-include-cleaner in LivenessAnalysis.cpp (NFC)Mehdi Amini
2023-08-11[MLIR][analysis] Fix call op handling in sparse backward dataflowSrishti Srivastava
Currently, data in `AbstractSparseBackwardDataFlowAnalysis` is considered to flow one-to-one, in order, from the operands of an op implementing `CallOpInterface` to the arguments of the function it is calling. This understanding of the data flow is inaccurate. The operands of such an op that forward to the function arguments are obtained using a method provided by `CallOpInterface` called `getArgOperands()`. This commit fixes this bug by using `getArgOperands()` instead of `getOperands()` to get the mapping from operands to function arguments because not all operands necessarily forward to the function arguments and even if they do, they don't necessarily have to be in the order in which they appear in the op. The operands that don't get forwarded are handled by the newly introduced `visitCallOperand()` function, which works analogous to the `visitBranchOperand()` function. This fix is also propagated to liveness analysis that earlier relied on this incorrect implementation of the sparse backward dataflow analysis framework and corrects some incorrect assumptions made in it. Extra cleanup: Improved a comment and removed an unnecessary code line. Signed-off-by: Srishti Srivastava <srishtisrivastava.ai@gmail.com> Reviewed By: matthiaskramm, jcai19 Differential Revision: https://reviews.llvm.org/D157261
2023-08-08[mlir][NFC] Make `ReturnLike` trait imply `RegionBranchTerminatorOpInterface`Markus Böck
This implication was already done de-facto and there were plenty of users and wrapper functions specifically used to handle the "return-like or RegionBranchTerminatorOpInterface" case. These simply existed due to up until recently missing features in ODS. With the new capabilities of traits, we can make `ReturnLike` imply `RegionBranchTerminatorOpInterface` and auto generate proper definitions for its methods. Various occurrences and wrapper methods used for `isa<RegionBranchTerminatorOpInterface>() || hasTrait<ReturnLike>()` have all been removed. Differential Revision: https://reviews.llvm.org/D157402
2023-07-29[MLIR][analysis] Fix error in the sparse backward dataflow analysisSrishti Srivastava
Earlier, in the sparse backward dataflow analysis, data from the results of an op implementing `RegionBranchOpInterface` was considered to flow into the operands of every op that did not implement the `RegionBranchTerminatorOpInterface` but was return-like and present in a region of the former. It was thus also expected that the number of results of the former be equal to the number of operands in the latter. This understanding of dataflow is incorrect and thus this expectation is also not justified. This commit fixes this incorrect understanding. This commit ensures that these return-like ops are handled just like the ops implementing the `RegionBranchTerminatorOpInterface`, which means that, if this op has a region `A` whose successors are regions `B`, `C`, and `D`, then data flows from the arguments (successor inputs) of `B`, `C`, and `D` to the corresponding successor operands of this op. This fix is also propagated to liveness analysis that earlier relied on this incorrect implementation of the sparse backward dataflow analysis framework and corrects some incorrect assumptions made in it. Also cleaned up some unnecessary comments from the test file. Issue: https://github.com/llvm/llvm-project/issues/64139. Signed-off-by: Srishti Srivastava <srishtisrivastava.ai@gmail.com> Reviewed By: jcai19, matthiaskramm, Mogball Differential Revision: https://reviews.llvm.org/D156376
2023-07-21[MLIR][ANALYSIS] Add liveness analysis utilitySrishti Srivastava
This commit adds a utility to implement liveness analysis using the sparse backward data-flow analysis framework. Theoretically, liveness analysis assigns liveness to each (value, program point) pair in the program and it is thus a dense analysis. However, since values are immutable in MLIR, a sparse analysis, which will assign liveness to each value in the program, suffices here. Liveness analysis has many applications. It can be used to avoid the computation of extraneous operations that have no effect on the memory or the final output of a program. It can also be used to optimize register allocation. Both of these applications help achieve one very important goal: reducing runtime. A value is considered "live" iff it: (1) has memory effects OR (2) is returned by a public function OR (3) is used to compute a value of type (1) or (2). It is also to be noted that a value could be of multiple types (1/2/3) at the same time. A value "has memory effects" iff it: (1.a) is an operand of an op with memory effects OR (1.b) is a non-forwarded branch operand and a block where its op could take the control has an op with memory effects. A value `A` is said to be "used to compute" value `B` iff `B` cannot be computed in the absence of `A`. Thus, in this implementation, we say that value `A` is used to compute value `B` iff: (3.a) `B` is a result of an op with operand `A` OR (3.b) `A` is used to compute some value `C` and `C` is used to compute `B`. --- It is important to note that there already exists an MLIR liveness utility here: llvm-project/mlir/include/mlir/Analysis/Liveness.h. So, what is the need for this new liveness analysis utility being added by this commit? That need is explained as follows:- The similarities between these two utilities is that both use the fixpoint iteration method to converge to the final result of liveness. And, both have the same theoretical understanding of liveness as well. However, the main difference between (a) the existing utility and (b) the added utility is the "scope of the analysis". (a) is restricted to analysing each block independently while (b) analyses blocks together, i.e., it looks at how the control flows from one block to the other, how a caller calls a callee, etc. The restriction in the former implies that some potentially non-live values could be marked live and thus the full potential of liveness analysis will not be realised. This can be understood using the example below: ``` 1 func.func private @private_dead_return_value_removal_0() -> (i32, i32) { 2 %0 = arith.constant 0 : i32 3 %1 = arith.addi %0, %0 : i32 4 return %0, %1 : i32, i32 5 } 6 func.func @public_dead_return_value_removal_0() -> (i32) { 7 %0:2 = func.call @private_dead_return_value_removal_0() : () -> (i32, i32) 8 return %0#0 : i32 9 } ``` Here, if we just restrict our analysis to a per-block basis like (a), we will say that the %1 on line 3 is live because it is computed and then returned outside its block by the function. But, if we perform a backward data-flow analysis like (b) does, we will say that %0#1 of line 7 is not live because it isn't returned by the public function and thus, %1 of line 3 is also not live. So, while (a) will be unable to suggest any IR optimizations, (b) can enable this IR to convert to:- ``` 1 func.func private @private_dead_return_value_removal_0() -> i32 { 2 %0 = arith.constant 0 : i32 3 return %0 : i32 4 } 5 func.func @public_dead_return_value_removal_0() -> i32 { 6 %0 = call @private_dead_return_value_removal_0() : () -> i32 7 return %0 : i32 8 } ``` One operation was removed and one unnecessary return value of the function was removed and the function signature was modified. This is an optimization that (b) can enable but (a) cannot. Such optimizations can help remove a lot of extraneous computations that are currently being done. Signed-off-by: Srishti Srivastava <srishtisrivastava.ai@gmail.com> Reviewed By: matthiaskramm, jcai19 Differential Revision: https://reviews.llvm.org/D153779