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