MLIR 23.0.0git
RemoveDeadValues.cpp
Go to the documentation of this file.
1//===- RemoveDeadValues.cpp - Remove Dead Values --------------------------===//
2//
3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4// See https://llvm.org/LICENSE.txt for license information.
5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6//
7//===----------------------------------------------------------------------===//
8//
9// The goal of this pass is optimization (reducing runtime) by removing
10// unnecessary instructions. Unlike other passes that rely on local information
11// gathered from patterns to accomplish optimization, this pass uses a full
12// analysis of the IR, specifically, liveness analysis, and is thus more
13// powerful.
14//
15// Currently, this pass performs the following optimizations:
16// (A) Removes function arguments that are not live,
17// (B) Removes function return values that are not live across all callers of
18// the function,
19// (C) Removes unneccesary operands, results, region arguments, and region
20// terminator operands of region branch ops, and,
21// (D) Removes simple and region branch ops that have all non-live results and
22// don't affect memory in any way,
23//
24// iff
25//
26// the IR doesn't have any non-function symbol ops, non-call symbol user ops and
27// branch ops.
28//
29// Here, a "simple op" refers to an op that isn't a symbol op, symbol-user op,
30// region branch op, branch op, region branch terminator op, or return-like.
31//
32//===----------------------------------------------------------------------===//
33
37#include "mlir/IR/Builders.h"
39#include "mlir/IR/Dialect.h"
40#include "mlir/IR/Operation.h"
42#include "mlir/IR/SymbolTable.h"
43#include "mlir/IR/Value.h"
44#include "mlir/IR/ValueRange.h"
45#include "mlir/IR/Visitors.h"
50#include "mlir/Pass/Pass.h"
51#include "mlir/Support/LLVM.h"
54#include "llvm/ADT/STLExtras.h"
55#include "llvm/Support/Debug.h"
56#include "llvm/Support/DebugLog.h"
57#include <cassert>
58#include <cstddef>
59#include <memory>
60#include <optional>
61#include <vector>
62
63#define DEBUG_TYPE "remove-dead-values"
64
65namespace mlir {
66#define GEN_PASS_DEF_REMOVEDEADVALUESPASS
67#include "mlir/Transforms/Passes.h.inc"
68} // namespace mlir
69
70using namespace mlir;
71using namespace mlir::dataflow;
72
73//===----------------------------------------------------------------------===//
74// RemoveDeadValues Pass
75//===----------------------------------------------------------------------===//
76
77namespace {
78
79// Set of structures below to be filled with operations and arguments to erase.
80// This is done to separate analysis and tree modification phases,
81// otherwise analysis is operating on half-deleted tree which is incorrect.
82
83struct FunctionToCleanUp {
84 FunctionOpInterface funcOp;
85 BitVector nonLiveArgs;
86 BitVector nonLiveRets;
87};
88
89struct ResultsToCleanup {
90 Operation *op;
91 BitVector nonLive;
92};
93
94struct OperandsToCleanup {
95 Operation *op;
96 BitVector nonLive;
97 // Optional: For CallOpInterface ops, stores the callee function.
98 Operation *callee = nullptr;
99 // Determines whether the operand should be replaced with a ub.poison result
100 // or erased entirely.
101 bool replaceWithPoison = false;
102};
103
104struct BlockArgsToCleanup {
105 Block *b;
106 BitVector nonLiveArgs;
107};
108
109struct SuccessorOperandsToCleanup {
110 BranchOpInterface branch;
111 unsigned successorIndex;
112 BitVector nonLiveOperands;
113};
114
115struct RDVFinalCleanupList {
116 SmallVector<Operation *> operations;
117 SmallVector<FunctionToCleanUp> functions;
118 SmallVector<OperandsToCleanup> operands;
119 SmallVector<ResultsToCleanup> results;
120 SmallVector<BlockArgsToCleanup> blocks;
121 SmallVector<SuccessorOperandsToCleanup> successorOperands;
122};
123
124// Some helper functions...
125
126/// Return true iff at least one value in `values` is live, given the liveness
127/// information in `la`.
128static bool hasLive(ValueRange values, const DenseSet<Value> &nonLiveSet,
130 for (Value value : values) {
131 if (nonLiveSet.contains(value)) {
132 LDBG() << "Value " << value << " is already marked non-live (dead)";
133 continue;
134 }
135
136 const Liveness *liveness = la.getLiveness(value);
137 if (!liveness) {
138 LDBG() << "Value " << value
139 << " has no liveness info, conservatively considered live";
140 return true;
141 }
142 if (liveness->isLive) {
143 LDBG() << "Value " << value << " is live according to liveness analysis";
144 return true;
145 } else {
146 LDBG() << "Value " << value << " is dead according to liveness analysis";
147 }
148 }
149 return false;
150}
151
152/// Return a BitVector of size `values.size()` where its i-th bit is 1 iff the
153/// i-th value in `values` is live, given the liveness information in `la`.
154static BitVector markLives(ValueRange values, const DenseSet<Value> &nonLiveSet,
156 BitVector lives(values.size(), true);
157
158 for (auto [index, value] : llvm::enumerate(values)) {
159 if (nonLiveSet.contains(value)) {
160 lives.reset(index);
161 LDBG() << "Value " << value
162 << " is already marked non-live (dead) at index " << index;
163 continue;
164 }
165
166 const Liveness *liveness = la.getLiveness(value);
167 // It is important to note that when `liveness` is null, we can't tell if
168 // `value` is live or not. So, the safe option is to consider it live. Also,
169 // the execution of this pass might create new SSA values when erasing some
170 // of the results of an op and we know that these new values are live
171 // (because they weren't erased) and also their liveness is null because
172 // liveness analysis ran before their creation.
173 if (!liveness) {
174 LDBG() << "Value " << value << " at index " << index
175 << " has no liveness info, conservatively considered live";
176 continue;
177 }
178 if (!liveness->isLive) {
179 lives.reset(index);
180 LDBG() << "Value " << value << " at index " << index
181 << " is dead according to liveness analysis";
182 } else {
183 LDBG() << "Value " << value << " at index " << index
184 << " is live according to liveness analysis";
185 }
186 }
187
188 return lives;
189}
190
191/// Collects values marked as "non-live" in the provided range and inserts them
192/// into the nonLiveSet. A value is considered "non-live" if the corresponding
193/// index in the `nonLive` bit vector is set.
194static void collectNonLiveValues(DenseSet<Value> &nonLiveSet, ValueRange range,
195 const BitVector &nonLive) {
196 for (auto [index, result] : llvm::enumerate(range)) {
197 if (!nonLive[index])
198 continue;
199 nonLiveSet.insert(result);
200 LDBG() << "Marking value " << result << " as non-live (dead) at index "
201 << index;
202 }
203}
204
205/// Drop the uses of the i-th result of `op` and then erase it iff toErase[i]
206/// is 1.
207static void dropUsesAndEraseResults(RewriterBase &rewriter, Operation *op,
208 BitVector toErase) {
209 assert(op->getNumResults() == toErase.size() &&
210 "expected the number of results in `op` and the size of `toErase` to "
211 "be the same");
212 for (auto idx : toErase.set_bits())
213 op->getResult(idx).dropAllUses();
214 rewriter.eraseOpResults(op, toErase);
215}
216
217/// Process a simple operation `op` using the liveness analysis `la`.
218/// If the operation has no memory effects and none of its results are live:
219/// 1. Add the operation to a list for future removal, and
220/// 2. Mark all its results as non-live values
221///
222/// The operation `op` is assumed to be simple. A simple operation is one that
223/// is NOT:
224/// - Function-like
225/// - Call-like
226/// - A region branch operation
227/// - A branch operation
228/// - A region branch terminator
229/// - Return-like
230static void processSimpleOp(Operation *op, RunLivenessAnalysis &la,
231 DenseSet<Value> &nonLiveSet,
232 RDVFinalCleanupList &cl) {
233 // Operations that have dead operands can be erased regardless of their
234 // side effects. The liveness analysis would not have marked an SSA value as
235 // "dead" if it had a side-effecting user that is reachable.
236 bool hasDeadOperand =
237 markLives(op->getOperands(), nonLiveSet, la).flip().any();
238 if (hasDeadOperand) {
239 LDBG() << "Simple op has dead operands, so the op must be dead: "
240 << OpWithFlags(op,
241 OpPrintingFlags().skipRegions().printGenericOpForm());
242 assert(!hasLive(op->getResults(), nonLiveSet, la) &&
243 "expected the op to have no live results");
244 cl.operations.push_back(op);
245 collectNonLiveValues(nonLiveSet, op->getResults(),
246 BitVector(op->getNumResults(), true));
247 return;
248 }
249
250 if (!isMemoryEffectFree(op) || hasLive(op->getResults(), nonLiveSet, la)) {
251 LDBG() << "Simple op is not memory effect free or has live results, "
252 "preserving it: "
253 << OpWithFlags(op,
254 OpPrintingFlags().skipRegions().printGenericOpForm());
255 return;
256 }
257
258 LDBG()
259 << "Simple op has all dead results and is memory effect free, scheduling "
260 "for removal: "
261 << OpWithFlags(op, OpPrintingFlags().skipRegions().printGenericOpForm());
262 cl.operations.push_back(op);
263 collectNonLiveValues(nonLiveSet, op->getResults(),
264 BitVector(op->getNumResults(), true));
265}
266
267/// Process a function-like operation `funcOp` using the liveness analysis `la`
268/// and the IR in `module`. If it is not public or external:
269/// (1) Adding its non-live arguments to a list for future removal.
270/// (2) Marking their corresponding operands in its callers for removal.
271/// (3) Identifying and enqueueing unnecessary terminator operands
272/// (return values that are non-live across all callers) for removal.
273/// (4) Enqueueing the non-live arguments and return values for removal.
274/// (5) Collecting the uses of these return values in its callers for future
275/// removal.
276/// (6) Marking all its results as non-live values.
277static void processFuncOp(FunctionOpInterface funcOp, Operation *module,
278 RunLivenessAnalysis &la, DenseSet<Value> &nonLiveSet,
279 RDVFinalCleanupList &cl) {
280 LDBG() << "Processing function op: "
281 << OpWithFlags(funcOp,
282 OpPrintingFlags().skipRegions().printGenericOpForm());
283 if (funcOp.isPublic() || funcOp.isExternal()) {
284 LDBG() << "Function is public or external, skipping: "
285 << funcOp.getOperation()->getName();
286 return;
287 }
288 SymbolTable::UseRange uses = *funcOp.getSymbolUses(module);
289 if (llvm::any_of(uses, [](SymbolTable::SymbolUse use) {
290 return !isa<CallOpInterface>(use.getUser());
291 })) {
292 // If a non-call operation references the function (e.g. spirv.EntryPoint),
293 // we cannot safely remove arguments or return values since we don't know
294 // what the user expects. Skip this function entirely.
295 return;
296 }
297 // Get the list of unnecessary (non-live) arguments in `nonLiveArgs`.
298 SmallVector<Value> arguments(funcOp.getArguments());
299 BitVector nonLiveArgs = markLives(arguments, nonLiveSet, la);
300 nonLiveArgs = nonLiveArgs.flip();
301
302 // Do (1).
303 for (auto [index, arg] : llvm::enumerate(arguments))
304 if (arg && nonLiveArgs[index])
305 nonLiveSet.insert(arg);
306
307 // Do (2). (Skip creating generic operand cleanup entries for call ops.
308 // Call arguments will be removed in the call-site specific segment-aware
309 // cleanup, avoiding generic eraseOperands bitvector mechanics.)
310 for (SymbolTable::SymbolUse use : uses) {
311 Operation *callOp = use.getUser();
312 // Push an empty operand cleanup entry so that call-site specific logic in
313 // cleanUpDeadVals runs (it keys off CallOpInterface). The BitVector is
314 // intentionally all false to avoid generic erasure.
315 // Store the funcOp as the callee to avoid expensive symbol lookup later.
316 cl.operands.push_back({callOp, BitVector(callOp->getNumOperands(), false),
317 funcOp.getOperation()});
318 }
319
320 // Do (3).
321 // Get the list of unnecessary terminator operands (return values that are
322 // non-live across all callers) in `nonLiveRets`. There is a very important
323 // subtlety here. Unnecessary terminator operands are NOT the operands of the
324 // terminator that are non-live. Instead, these are the return values of the
325 // callers such that a given return value is non-live across all callers. Such
326 // corresponding operands in the terminator could be live. An example to
327 // demonstrate this:
328 // func.func private @f(%arg0: memref<i32>) -> (i32, i32) {
329 // %c0_i32 = arith.constant 0 : i32
330 // %0 = arith.addi %c0_i32, %c0_i32 : i32
331 // memref.store %0, %arg0[] : memref<i32>
332 // return %c0_i32, %0 : i32, i32
333 // }
334 // func.func @main(%arg0: i32, %arg1: memref<i32>) -> (i32) {
335 // %1:2 = call @f(%arg1) : (memref<i32>) -> i32
336 // return %1#0 : i32
337 // }
338 // Here, we can see that %1#1 is never used. It is non-live. Thus, @f doesn't
339 // need to return %0. But, %0 is live. And, still, we want to stop it from
340 // being returned, in order to optimize our IR. So, this demonstrates how we
341 // can make our optimization strong by even removing a live return value (%0),
342 // since it forwards only to non-live value(s) (%1#1).
343 size_t numReturns = funcOp.getNumResults();
344 BitVector nonLiveRets(numReturns, true);
345 for (SymbolTable::SymbolUse use : uses) {
346 Operation *callOp = use.getUser();
347 assert(isa<CallOpInterface>(callOp) && "expected a call-like user");
348 BitVector liveCallRets = markLives(callOp->getResults(), nonLiveSet, la);
349 nonLiveRets &= liveCallRets.flip();
350 }
351
352 // Note that in the absence of control flow ops forcing the control to go from
353 // the entry (first) block to the other blocks, the control never reaches any
354 // block other than the entry block, because every block has a terminator.
355 for (Block &block : funcOp.getBlocks()) {
356 Operation *returnOp = block.getTerminator();
357 if (!returnOp->hasTrait<OpTrait::ReturnLike>())
358 continue;
359 if (returnOp && returnOp->getNumOperands() == numReturns)
360 cl.operands.push_back({returnOp, nonLiveRets});
361 }
362
363 // Do (4).
364 cl.functions.push_back({funcOp, nonLiveArgs, nonLiveRets});
365
366 // Do (5) and (6).
367 if (numReturns == 0)
368 return;
369 for (SymbolTable::SymbolUse use : uses) {
370 Operation *callOp = use.getUser();
371 assert(isa<CallOpInterface>(callOp) && "expected a call-like user");
372 cl.results.push_back({callOp, nonLiveRets});
373 collectNonLiveValues(nonLiveSet, callOp->getResults(), nonLiveRets);
374 }
375}
376
377/// Process a region branch operation `regionBranchOp` using the liveness
378/// information in `la`. The processing involves two scenarios:
379///
380/// Scenario 1: If the operation has no memory effects and none of its results
381/// are live:
382/// 1.1. Enqueue all its uses for deletion.
383/// 1.2. Enqueue the branch itself for deletion.
384///
385/// Scenario 2: Otherwise:
386/// 2.1. Find all operands that are forwarded to only dead region successor
387/// inputs. I.e., forwarded to block arguments / op results that we do
388/// not want to keep.
389/// 2.2. Also find operands who's values are dead (i.e., are scheduled for
390/// erasure) due to other operations.
391/// 2.3. Enqueue all such operands for replacement with ub.poison.
392///
393/// Note: In scenario 2, block arguments and op results are not removed.
394/// However, the IR is simplified such that canonicalization patterns can
395/// remove them later.
396static void processRegionBranchOp(RegionBranchOpInterface regionBranchOp,
398 DenseSet<Value> &nonLiveSet,
399 RDVFinalCleanupList &cl) {
400 LDBG() << "Processing region branch op: "
401 << OpWithFlags(regionBranchOp,
402 OpPrintingFlags().skipRegions().printGenericOpForm());
403
404 // Scenario 1. This is the only case where the entire `regionBranchOp`
405 // is removed. It will not happen in any other scenario. Note that in this
406 // case, a non-forwarded operand of `regionBranchOp` could be live/non-live.
407 // It could never be live because of this op but its liveness could have been
408 // attributed to something else.
409 if (isMemoryEffectFree(regionBranchOp.getOperation()) &&
410 !hasLive(regionBranchOp->getResults(), nonLiveSet, la)) {
411 cl.operations.push_back(regionBranchOp.getOperation());
412 return;
413 }
414
415 // Mapping from operands to forwarded successor inputs. An operand can be
416 // forwarded to multiple successors.
417 //
418 // Example:
419 //
420 // %0 = scf.while : () -> i32 {
421 // scf.condition(...) %forwarded_value : i32
422 // } do {
423 // ^bb0(%arg0: i32):
424 // scf.yield
425 // }
426 // // No uses of %0.
427 //
428 // In the above example, %forwarded_value is forwarded to %arg0 and %0. Both
429 // %arg0 and %0 are dead, so %forwarded_value can be replaced with a
430 // ub.poison result.
431 //
432 // operandToSuccessorInputs[%forwarded_value] = {%arg0, %0}
433 //
434 RegionBranchSuccessorMapping operandToSuccessorInputs;
435 regionBranchOp.getSuccessorOperandInputMapping(operandToSuccessorInputs);
436
437 DenseMap<Operation *, BitVector> deadOperandsPerOp;
438 for (auto [opOperand, successorInputs] : operandToSuccessorInputs) {
439 // Helper function to mark the operand as dead, to be replaced with a
440 // ub.poison result.
441 auto markOperandDead = [&opOperand = opOperand, &deadOperandsPerOp]() {
442 // Create an entry in `deadOperandsPerOp` (initialized to "false", i.e.,
443 // no "dead" op operands) if it's the first time that we are seeing an op
444 // operand for this op. Otherwise, just take the existing bit vector from
445 // the map.
446 BitVector &deadOperands =
447 deadOperandsPerOp
448 .try_emplace(opOperand->getOwner(),
449 opOperand->getOwner()->getNumOperands(), false)
450 .first->second;
451 deadOperands.set(opOperand->getOperandNumber());
452 };
453
454 // The operand value is scheduled for removal. Mark it as dead.
455 if (!hasLive(opOperand->get(), nonLiveSet, la)) {
456 markOperandDead();
457 continue;
458 }
459
460 // If one of the successor inputs is live, the respective operand must be
461 // kept. Otherwise, ub.poison can be passed as operand.
462 if (!hasLive(successorInputs, nonLiveSet, la))
463 markOperandDead();
464 }
465
466 for (auto [op, deadOperands] : deadOperandsPerOp) {
467 cl.operands.push_back(
468 {op, deadOperands, nullptr, /*replaceWithPoison=*/true});
469 }
470}
471
472/// Steps to process a `BranchOpInterface` operation:
473///
474/// When a non-forwarded operand is dead (e.g., the condition value of a
475/// conditional branch op), the entire operation is dead.
476///
477/// Otherwise, iterate through each successor block of `branchOp`.
478/// (1) For each successor block, gather all operands from all successors.
479/// (2) Fetch their associated liveness analysis data and collect for future
480/// removal.
481/// (3) Identify and collect the dead operands from the successor block
482/// as well as their corresponding arguments.
483
484static void processBranchOp(BranchOpInterface branchOp, RunLivenessAnalysis &la,
485 DenseSet<Value> &nonLiveSet,
486 RDVFinalCleanupList &cl) {
487 LDBG() << "Processing branch op: " << *branchOp;
488
489 // Check for dead non-forwarded operands.
490 BitVector deadNonForwardedOperands =
491 markLives(branchOp->getOperands(), nonLiveSet, la).flip();
492 unsigned numSuccessors = branchOp->getNumSuccessors();
493 for (unsigned succIdx = 0; succIdx < numSuccessors; ++succIdx) {
494 SuccessorOperands successorOperands =
495 branchOp.getSuccessorOperands(succIdx);
496 // Remove all non-forwarded operands from the bit vector.
497 for (OpOperand &opOperand : successorOperands.getMutableForwardedOperands())
498 deadNonForwardedOperands[opOperand.getOperandNumber()] = false;
499 }
500 if (deadNonForwardedOperands.any()) {
501 cl.operations.push_back(branchOp.getOperation());
502 return;
503 }
504
505 for (unsigned succIdx = 0; succIdx < numSuccessors; ++succIdx) {
506 Block *successorBlock = branchOp->getSuccessor(succIdx);
507
508 // Do (1)
509 SuccessorOperands successorOperands =
510 branchOp.getSuccessorOperands(succIdx);
511 SmallVector<Value> operandValues;
512 for (unsigned operandIdx = 0; operandIdx < successorOperands.size();
513 ++operandIdx) {
514 operandValues.push_back(successorOperands[operandIdx]);
515 }
516
517 // Do (2)
518 BitVector successorNonLive =
519 markLives(operandValues, nonLiveSet, la).flip();
520 collectNonLiveValues(nonLiveSet, successorBlock->getArguments(),
521 successorNonLive);
522
523 // Do (3)
524 cl.blocks.push_back({successorBlock, successorNonLive});
525 cl.successorOperands.push_back({branchOp, succIdx, successorNonLive});
526 }
527}
528
529/// Create ub.poison ops for the given values. If a value has no uses, return
530/// an "empty" value.
531static SmallVector<Value> createPoisonedValues(OpBuilder &b,
532 ValueRange values) {
533 return llvm::map_to_vector(values, [&](Value value) {
534 if (value.use_empty())
535 return Value();
536 return ub::PoisonOp::create(b, value.getLoc(), value.getType()).getResult();
537 });
538}
539
540namespace {
541/// A listener that keeps track of ub.poison ops.
542struct TrackingListener : public RewriterBase::Listener {
543 void notifyOperationErased(Operation *op) override {
544 if (auto poisonOp = dyn_cast<ub::PoisonOp>(op))
545 poisonOps.erase(poisonOp);
546 }
547 void notifyOperationInserted(Operation *op,
548 OpBuilder::InsertPoint previous) override {
549 if (auto poisonOp = dyn_cast<ub::PoisonOp>(op))
550 poisonOps.insert(poisonOp);
551 }
552 DenseSet<ub::PoisonOp> poisonOps;
553};
554} // namespace
555
556/// Removes dead values collected in RDVFinalCleanupList.
557/// To be run once when all dead values have been collected.
558static void cleanUpDeadVals(MLIRContext *ctx, RDVFinalCleanupList &list) {
559 LDBG() << "Starting cleanup of dead values...";
560
561 // New ub.poison ops may be inserted during cleanup. Some of these ops may no
562 // longer be needed after the cleanup. A tracking listener keeps track of all
563 // new ub.poison ops, so that they can be removed again after the cleanup.
564 TrackingListener listener;
565 IRRewriter rewriter(ctx, &listener);
566
567 // 1. Blocks, We must remove the block arguments and successor operands before
568 // deleting the operation, as they may reside in the region operation.
569 LDBG() << "Cleaning up " << list.blocks.size() << " block argument lists";
570 for (auto &b : list.blocks) {
571 // blocks that are accessed via multiple codepaths processed once
572 if (b.b->getNumArguments() != b.nonLiveArgs.size())
573 continue;
574 LDBG_OS([&](raw_ostream &os) {
575 os << "Erasing non-live arguments [";
576 llvm::interleaveComma(b.nonLiveArgs.set_bits(), os);
577 os << "] from block #" << b.b->computeBlockNumber() << " in region #"
578 << b.b->getParent()->getRegionNumber() << " of operation "
579 << OpWithFlags(b.b->getParent()->getParentOp(),
580 OpPrintingFlags().skipRegions().printGenericOpForm());
581 });
582 // Note: Iterate from the end to make sure that that indices of not yet
583 // processes arguments do not change.
584 for (int i = b.nonLiveArgs.size() - 1; i >= 0; --i) {
585 if (!b.nonLiveArgs[i])
586 continue;
587 b.b->getArgument(i).dropAllUses();
588 b.b->eraseArgument(i);
589 }
590 }
591
592 // 2. Successor Operands
593 LDBG() << "Cleaning up " << list.successorOperands.size()
594 << " successor operand lists";
595 for (auto &op : list.successorOperands) {
596 SuccessorOperands successorOperands =
597 op.branch.getSuccessorOperands(op.successorIndex);
598 // blocks that are accessed via multiple codepaths processed once
599 if (successorOperands.size() != op.nonLiveOperands.size())
600 continue;
601 LDBG_OS([&](raw_ostream &os) {
602 os << "Erasing non-live successor operands [";
603 llvm::interleaveComma(op.nonLiveOperands.set_bits(), os);
604 os << "] from successor " << op.successorIndex << " of branch: "
605 << OpWithFlags(op.branch.getOperation(),
606 OpPrintingFlags().skipRegions().printGenericOpForm());
607 });
608 // it iterates backwards because erase invalidates all successor indexes
609 for (int i = successorOperands.size() - 1; i >= 0; --i) {
610 if (!op.nonLiveOperands[i])
611 continue;
612 successorOperands.erase(i);
613 }
614 }
615
616 // 3. Functions
617 LDBG() << "Cleaning up " << list.functions.size() << " functions";
618 // Record which function arguments were erased so we can shrink call-site
619 // argument segments for CallOpInterface operations (e.g. ops using
620 // AttrSizedOperandSegments) in the next phase.
622 for (auto &f : list.functions) {
623 LDBG() << "Cleaning up function: " << f.funcOp.getName() << " ("
624 << f.funcOp.getOperation() << ")";
625 LDBG_OS([&](raw_ostream &os) {
626 os << " Erasing non-live arguments [";
627 llvm::interleaveComma(f.nonLiveArgs.set_bits(), os);
628 os << "]\n";
629 os << " Erasing non-live return values [";
630 llvm::interleaveComma(f.nonLiveRets.set_bits(), os);
631 os << "]";
632 });
633 // Drop all uses of the dead arguments.
634 for (auto deadIdx : f.nonLiveArgs.set_bits())
635 f.funcOp.getArgument(deadIdx).dropAllUses();
636 // Some functions may not allow erasing arguments or results. These calls
637 // return failure in such cases without modifying the function, so it's okay
638 // to proceed.
639 if (succeeded(f.funcOp.eraseArguments(f.nonLiveArgs))) {
640 // Record only if we actually erased something.
641 if (f.nonLiveArgs.any())
642 erasedFuncArgs.try_emplace(f.funcOp.getOperation(), f.nonLiveArgs);
643 } else {
644 LDBG() << "Failed to erase arguments for function: "
645 << f.funcOp.getName();
646 }
647 (void)f.funcOp.eraseResults(f.nonLiveRets);
648 }
649
650 // 4. Operands
651 LDBG() << "Cleaning up " << list.operands.size() << " operand lists";
652 for (OperandsToCleanup &o : list.operands) {
653 // Handle call-specific cleanup only when we have a cached callee reference.
654 // This avoids expensive symbol lookup and is defensive against future
655 // changes.
656 bool handledAsCall = false;
657 if (o.callee && isa<CallOpInterface>(o.op)) {
658 auto call = cast<CallOpInterface>(o.op);
659 auto it = erasedFuncArgs.find(o.callee);
660 if (it != erasedFuncArgs.end()) {
661 const BitVector &deadArgIdxs = it->second;
662 MutableOperandRange args = call.getArgOperandsMutable();
663 // First, erase the call arguments corresponding to erased callee
664 // args. We iterate backwards to preserve indices.
665 for (unsigned argIdx : llvm::reverse(deadArgIdxs.set_bits()))
666 args.erase(argIdx);
667 // If this operand cleanup entry also has a generic nonLive bitvector,
668 // clear bits for call arguments we already erased above to avoid
669 // double-erasing (which could impact other segments of ops with
670 // AttrSizedOperandSegments).
671 if (o.nonLive.any()) {
672 // Map the argument logical index to the operand number(s) recorded.
673 int operandOffset = call.getArgOperands().getBeginOperandIndex();
674 for (int argIdx : deadArgIdxs.set_bits()) {
675 int operandNumber = operandOffset + argIdx;
676 if (operandNumber < static_cast<int>(o.nonLive.size()))
677 o.nonLive.reset(operandNumber);
678 }
679 }
680 handledAsCall = true;
681 }
682 }
683 // Perform generic operand erasure for:
684 // - Non-call operations
685 // - Call operations without cached callee (where handledAsCall is false)
686 // But skip call operations that were already handled via segment-aware path
687 if (!handledAsCall && o.nonLive.any()) {
688 LDBG_OS([&](raw_ostream &os) {
689 os << "Erasing non-live operands [";
690 llvm::interleaveComma(o.nonLive.set_bits(), os);
691 os << "] from operation: "
692 << OpWithFlags(o.op,
693 OpPrintingFlags().skipRegions().printGenericOpForm());
694 });
695 if (o.replaceWithPoison) {
696 rewriter.setInsertionPoint(o.op);
697 for (auto deadIdx : o.nonLive.set_bits()) {
698 o.op->setOperand(
699 deadIdx, createPoisonedValues(rewriter, o.op->getOperand(deadIdx))
700 .front());
701 }
702 } else {
703 o.op->eraseOperands(o.nonLive);
704 }
705 }
706 }
707
708 // 5. Results
709 LDBG() << "Cleaning up " << list.results.size() << " result lists";
710 for (auto &r : list.results) {
711 LDBG_OS([&](raw_ostream &os) {
712 os << "Erasing non-live results [";
713 llvm::interleaveComma(r.nonLive.set_bits(), os);
714 os << "] from operation: "
715 << OpWithFlags(r.op,
716 OpPrintingFlags().skipRegions().printGenericOpForm());
717 });
718 dropUsesAndEraseResults(rewriter, r.op, r.nonLive);
719 }
720
721 // 6. Operations
722 LDBG() << "Cleaning up " << list.operations.size() << " operations";
723 for (Operation *op : list.operations) {
724 LDBG() << "Erasing operation: "
725 << OpWithFlags(op,
726 OpPrintingFlags().skipRegions().printGenericOpForm());
727 rewriter.setInsertionPoint(op);
728 if (op->hasTrait<OpTrait::IsTerminator>()) {
729 // When erasing a terminator, insert an unreachable op in its place.
730 ub::UnreachableOp::create(rewriter, op->getLoc());
731 }
732
733 // Before erasing the operation, replace all result values with live-uses by
734 // ub.poison values. This is important to maintain IR validity. For example,
735 // if we have an op with one of its results used by another op, erasing the
736 // op without replacing its corresponding result would leave us with a
737 // dangling operand in the user op. By replacing the result with a ub.poison
738 // value, we ensure that the user op still has a valid operand, even though
739 // it's a poison value which will be cleaned up later if it can be cleaned
740 // up. This keeps the IR valid for further simplification and
741 // canonicalization.
742 auto opResults = op->getResults();
743 for (Value opResult : opResults) {
744 // Early continue for the case where the op result has no uses. No need to
745 // create a poison op here.
746 if (opResult.use_empty())
747 continue;
748
749 rewriter.setInsertionPoint(op);
750 Value poisonedValue = createPoisonedValues(rewriter, opResult).front();
751 rewriter.replaceAllUsesWith(opResult, poisonedValue);
752 }
753
754 op->dropAllUses();
755 rewriter.eraseOp(op);
756 }
757
758 // 7. Remove all dead poison ops.
759 for (ub::PoisonOp poisonOp : listener.poisonOps) {
760 if (poisonOp.use_empty())
761 poisonOp.erase();
762 }
763
764 LDBG() << "Finished cleanup of dead values";
765}
766
767struct RemoveDeadValues
768 : public impl::RemoveDeadValuesPassBase<RemoveDeadValues> {
769 using impl::RemoveDeadValuesPassBase<
770 RemoveDeadValues>::RemoveDeadValuesPassBase;
771 void runOnOperation() override;
772};
773} // namespace
774
775void RemoveDeadValues::runOnOperation() {
776 auto &la = getAnalysis<RunLivenessAnalysis>();
777 Operation *module = getOperation();
778
779 // Tracks values eligible for erasure - complements liveness analysis to
780 // identify "droppable" values.
781 DenseSet<Value> deadVals;
782
783 // Maintains a list of Ops, values, branches, etc., slated for cleanup at the
784 // end of this pass.
785 RDVFinalCleanupList finalCleanupList;
786
787 module->walk([&](Operation *op) {
788 if (auto funcOp = dyn_cast<FunctionOpInterface>(op)) {
789 processFuncOp(funcOp, module, la, deadVals, finalCleanupList);
790 } else if (auto regionBranchOp = dyn_cast<RegionBranchOpInterface>(op)) {
791 processRegionBranchOp(regionBranchOp, la, deadVals, finalCleanupList);
792 } else if (auto branchOp = dyn_cast<BranchOpInterface>(op)) {
793 processBranchOp(branchOp, la, deadVals, finalCleanupList);
794 } else if (op->hasTrait<::mlir::OpTrait::IsTerminator>()) {
795 // Nothing to do here because this is a terminator op and it should be
796 // honored with respect to its parent
797 } else if (isa<CallOpInterface>(op)) {
798 // Nothing to do because this op is associated with a function op and gets
799 // cleaned when the latter is cleaned.
800 } else {
801 processSimpleOp(op, la, deadVals, finalCleanupList);
802 }
803 });
804
805 MLIRContext *context = module->getContext();
806 cleanUpDeadVals(context, finalCleanupList);
807
808 if (!canonicalize)
809 return;
810
811 // Canonicalize all region branch ops.
812 SmallVector<Operation *> opsToCanonicalize;
813 module->walk([&](RegionBranchOpInterface regionBranchOp) {
814 opsToCanonicalize.push_back(regionBranchOp.getOperation());
815 });
816 // Collect all canonicalization patterns for region branch ops.
817 RewritePatternSet owningPatterns(context);
818 DenseSet<RegisteredOperationName> populatedPatterns;
819 for (Operation *op : opsToCanonicalize)
820 if (std::optional<RegisteredOperationName> info = op->getRegisteredInfo())
821 if (populatedPatterns.insert(*info).second)
822 info->getCanonicalizationPatterns(owningPatterns, context);
823 if (failed(applyOpPatternsGreedily(opsToCanonicalize,
824 std::move(owningPatterns)))) {
825 module->emitError("greedy pattern rewrite failed to converge");
826 signalPassFailure();
827 }
828}
b
Return true if permutation is a valid permutation of the outer_dims_perm (case OuterOrInnerPerm::Oute...
Block represents an ordered list of Operations.
Definition Block.h:33
BlockArgListType getArguments()
Definition Block.h:97
Block * getSuccessor(unsigned i)
Definition Block.cpp:274
This class coordinates rewriting a piece of IR outside of a pattern rewrite, providing a way to keep ...
MLIRContext is the top-level object for a collection of MLIR operations.
Definition MLIRContext.h:63
This class provides a mutable adaptor for a range of operands.
Definition ValueRange.h:118
void erase(unsigned subStart, unsigned subLen=1)
Erase the operands within the given sub-range.
This class helps build Operations.
Definition Builders.h:209
void setInsertionPoint(Block *block, Block::iterator insertPoint)
Set the insertion point to the specified location.
Definition Builders.h:400
This class represents an operand of an operation.
Definition Value.h:257
Set of flags used to control the behavior of the various IR print methods (e.g.
This class provides the API for ops that are known to be terminators.
A wrapper class that allows for printing an operation with a set of flags, useful to act as a "stream...
Definition Operation.h:1119
Operation is the basic unit of execution within MLIR.
Definition Operation.h:88
Value getOperand(unsigned idx)
Definition Operation.h:358
bool hasTrait()
Returns true if the operation was registered with a particular trait, e.g.
Definition Operation.h:757
void dropAllUses()
Drop all uses of results of this operation.
Definition Operation.h:842
void setOperand(unsigned idx, Value value)
Definition Operation.h:359
void eraseOperands(unsigned idx, unsigned length=1)
Erase the operands starting at position idx and ending at position 'idx'+'length'.
Definition Operation.h:368
OpResult getResult(unsigned idx)
Get the 'idx'th result of this operation.
Definition Operation.h:415
Location getLoc()
The source location the operation was defined or derived from.
Definition Operation.h:223
std::optional< RegisteredOperationName > getRegisteredInfo()
If this operation has a registered operation description, return it.
Definition Operation.h:123
unsigned getNumOperands()
Definition Operation.h:354
operand_range getOperands()
Returns an iterator on the underlying Value's.
Definition Operation.h:386
result_range getResults()
Definition Operation.h:423
unsigned getNumResults()
Return the number of results held by this operation.
Definition Operation.h:412
This class coordinates the application of a rewrite on a set of IR, providing a way for clients to tr...
virtual void eraseOp(Operation *op)
This method erases an operation that is known to have no uses.
Operation * eraseOpResults(Operation *op, const BitVector &eraseIndices)
Erase the specified results of the given operation.
virtual void replaceAllUsesWith(Value from, Value to)
Find uses of from and replace them with to.
This class models how operands are forwarded to block arguments in control flow.
void erase(unsigned subStart, unsigned subLen=1)
Erase operands forwarded to the successor.
MutableOperandRange getMutableForwardedOperands() const
Get the range of operands that are simply forwarded to the successor.
unsigned size() const
Returns the amount of operands passed to the successor.
This class represents a specific symbol use.
Operation * getUser() const
Return the operation user of this symbol reference.
This class implements a range of SymbolRef uses.
This class provides an abstraction over the different types of ranges over Values.
Definition ValueRange.h:387
This class represents an instance of an SSA value in the MLIR system, representing a computable value...
Definition Value.h:96
bool use_empty() const
Returns true if this value has no uses.
Definition Value.h:208
void dropAllUses()
Drop all uses of this object from their respective owners.
Definition Value.h:144
Type getType() const
Return the type of this value.
Definition Value.h:105
Location getLoc() const
Return the location of this value.
Definition Value.cpp:24
detail::InFlightRemark failed(Location loc, RemarkOpts opts)
Report an optimization remark that failed.
Definition Remarks.h:717
Include the generated interface declarations.
DenseMap< OpOperand *, SmallVector< Value > > RegionBranchSuccessorMapping
A mapping from successor operands to successor inputs.
llvm::DenseSet< ValueT, ValueInfoT > DenseSet
Definition LLVM.h:120
bool isMemoryEffectFree(Operation *op)
Returns true if the given operation is free of memory effects.
LogicalResult applyOpPatternsGreedily(ArrayRef< Operation * > ops, const FrozenRewritePatternSet &patterns, GreedyRewriteConfig config=GreedyRewriteConfig(), bool *changed=nullptr, bool *allErased=nullptr)
Rewrite the specified ops by repeatedly applying the highest benefit patterns in a greedy worklist dr...
llvm::DenseMap< KeyT, ValueT, KeyInfoT, BucketT > DenseMap
Definition LLVM.h:118
This trait indicates that a terminator operation is "return-like".
This lattice represents, for a given value, whether or not it is "live".
Runs liveness analysis on the IR defined by op.
const Liveness * getLiveness(Value val)