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