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