Skip to content

Commit b83d8dc

Browse files
author
Xin Liu
committed
[MLIR][RemoveDeadValues] Mark arguments of a public function Live
This diff also changes traversal order from forward to backward for region/block/ops. This order guanratees Liveness updates at a callsite can propagates to the defs of arguments. ``` ./bin/llvm-lit -v ../mlir/test/Transforms/remove-dead-values.mlir ```
1 parent cafc064 commit b83d8dc

File tree

3 files changed

+78
-6
lines changed

3 files changed

+78
-6
lines changed

mlir/include/mlir/IR/Visitors.h

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,20 @@ struct ForwardIterator {
3939
}
4040
};
4141

42+
/// This iterator enumerates the elements in "backward" order.
43+
struct BackwardIterator {
44+
template <typename T>
45+
static auto makeIterable(T &range) {
46+
if constexpr (std::is_same<T, Operation>()) {
47+
/// Make operations iterable: return the list of regions.
48+
return llvm::reverse(range.getRegions());
49+
} else {
50+
/// Regions and block are already iterable.
51+
return llvm::reverse(range);
52+
}
53+
}
54+
};
55+
4256
/// A utility class to encode the current walk stage for "generic" walkers.
4357
/// When walking an operation, we can either choose a Pre/Post order walker
4458
/// which invokes the callback on an operation before/after all its attached

mlir/lib/Transforms/RemoveDeadValues.cpp

Lines changed: 46 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -115,9 +115,15 @@ struct RDVFinalCleanupList {
115115

116116
/// Return true iff at least one value in `values` is live, given the liveness
117117
/// information in `la`.
118-
static bool hasLive(ValueRange values, const DenseSet<Value> &nonLiveSet,
118+
static bool hasLive(ValueRange values, const DenseSet<Value> &nonLiveSet, const DenseSet<Value> &liveSet,
119+
119120
RunLivenessAnalysis &la) {
120121
for (Value value : values) {
122+
if (liveSet.contains(value)) {
123+
LDBG() << "Value " << value << " is marked live by CallOp";
124+
return true;
125+
}
126+
121127
if (nonLiveSet.contains(value)) {
122128
LDBG() << "Value " << value << " is already marked non-live (dead)";
123129
continue;
@@ -257,8 +263,9 @@ static SmallVector<OpOperand *> operandsToOpOperands(OperandRange operands) {
257263
/// - Return-like
258264
static void processSimpleOp(Operation *op, RunLivenessAnalysis &la,
259265
DenseSet<Value> &nonLiveSet,
266+
DenseSet<Value> &liveSet,
260267
RDVFinalCleanupList &cl) {
261-
if (!isMemoryEffectFree(op) || hasLive(op->getResults(), nonLiveSet, la)) {
268+
if (!isMemoryEffectFree(op) || hasLive(op->getResults(), nonLiveSet, liveSet, la)) {
262269
LDBG() << "Simple op is not memory effect free or has live results, "
263270
"preserving it: "
264271
<< OpWithFlags(op, OpPrintingFlags().skipRegions());
@@ -376,6 +383,31 @@ static void processFuncOp(FunctionOpInterface funcOp, Operation *module,
376383
}
377384
}
378385

386+
static void processCallOp(CallOpInterface callOp, Operation *module,
387+
RunLivenessAnalysis &la,
388+
DenseSet<Value> &liveSet) {
389+
auto callable = callOp.getCallableForCallee();
390+
391+
if (auto symbolRef = callable.dyn_cast<SymbolRefAttr>()) {
392+
Operation *calleeOp = SymbolTable::lookupSymbolIn(module, symbolRef);
393+
394+
if (auto funcOp = llvm::dyn_cast_or_null<mlir::FunctionOpInterface>(calleeOp)) {
395+
// Ensure the outgoing arguments of PUBLIC functions are live
396+
// because processFuncOp can not process them.
397+
//
398+
// Liveness treats the external function as a blackbox.
399+
if (funcOp.isPublic()) {
400+
for (Value arg: callOp.getArgOperands()) {
401+
const Liveness *liveness = la.getLiveness(arg);
402+
if (liveness && !liveness->isLive) {
403+
liveSet.insert(arg);
404+
}
405+
}
406+
}
407+
}
408+
}
409+
}
410+
379411
/// Process a region branch operation `regionBranchOp` using the liveness
380412
/// information in `la`. The processing involves two scenarios:
381413
///
@@ -408,6 +440,7 @@ static void processFuncOp(FunctionOpInterface funcOp, Operation *module,
408440
static void processRegionBranchOp(RegionBranchOpInterface regionBranchOp,
409441
RunLivenessAnalysis &la,
410442
DenseSet<Value> &nonLiveSet,
443+
DenseSet<Value> &liveSet,
411444
RDVFinalCleanupList &cl) {
412445
LDBG() << "Processing region branch op: "
413446
<< OpWithFlags(regionBranchOp, OpPrintingFlags().skipRegions());
@@ -616,7 +649,7 @@ static void processRegionBranchOp(RegionBranchOpInterface regionBranchOp,
616649
// attributed to something else.
617650
// Do (1') and (2').
618651
if (isMemoryEffectFree(regionBranchOp.getOperation()) &&
619-
!hasLive(regionBranchOp->getResults(), nonLiveSet, la)) {
652+
!hasLive(regionBranchOp->getResults(), nonLiveSet, liveSet, la)) {
620653
cl.operations.push_back(regionBranchOp.getOperation());
621654
return;
622655
}
@@ -834,16 +867,18 @@ void RemoveDeadValues::runOnOperation() {
834867
// Tracks values eligible for erasure - complements liveness analysis to
835868
// identify "droppable" values.
836869
DenseSet<Value> deadVals;
870+
// mark outgoing arguments to a public function LIVE.
871+
DenseSet<Value> liveVals;
837872

838873
// Maintains a list of Ops, values, branches, etc., slated for cleanup at the
839874
// end of this pass.
840875
RDVFinalCleanupList finalCleanupList;
841876

842-
module->walk([&](Operation *op) {
877+
module->walk<WalkOrder::PostOrder, BackwardIterator>([&](Operation *op) {
843878
if (auto funcOp = dyn_cast<FunctionOpInterface>(op)) {
844879
processFuncOp(funcOp, module, la, deadVals, finalCleanupList);
845880
} else if (auto regionBranchOp = dyn_cast<RegionBranchOpInterface>(op)) {
846-
processRegionBranchOp(regionBranchOp, la, deadVals, finalCleanupList);
881+
processRegionBranchOp(regionBranchOp, la, deadVals, liveVals, finalCleanupList);
847882
} else if (auto branchOp = dyn_cast<BranchOpInterface>(op)) {
848883
processBranchOp(branchOp, la, deadVals, finalCleanupList);
849884
} else if (op->hasTrait<::mlir::OpTrait::IsTerminator>()) {
@@ -852,8 +887,13 @@ void RemoveDeadValues::runOnOperation() {
852887
} else if (isa<CallOpInterface>(op)) {
853888
// Nothing to do because this op is associated with a function op and gets
854889
// cleaned when the latter is cleaned.
890+
//
891+
// The only exception is public callee. By default, Liveness analysis is inter-procedural.
892+
// Unused arguments of a public function nonLive and are propagated to the caller.
893+
// processCallOp puts them to liveVals.
894+
processCallOp(cast<CallOpInterface>(op), module, la, liveVals);
855895
} else {
856-
processSimpleOp(op, la, deadVals, finalCleanupList);
896+
processSimpleOp(op, la, deadVals, liveVals, finalCleanupList);
857897
}
858898
});
859899

mlir/test/Transforms/remove-dead-values.mlir

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -569,6 +569,24 @@ module @return_void_with_unused_argument {
569569
call @fn_return_void_with_unused_argument(%arg0, %unused) : (i32, memref<4xi32>) -> ()
570570
return %unused : memref<4xi32>
571571
}
572+
573+
// the function is immutable because it is public.
574+
func.func public @immutable_fn_return_void_with_unused_argument(%arg0: i32, %unused: i32) -> () {
575+
%sum = arith.addi %arg0, %arg0 : i32
576+
%c0 = arith.constant 0 : index
577+
%buf = memref.alloc() : memref<1xi32>
578+
memref.store %sum, %buf[%c0] : memref<1xi32>
579+
return
580+
}
581+
// CHECK-LABEL: func.func @main2
582+
// CHECK-SAME: (%[[ARG0_MAIN:.*]]: i32)
583+
// CHECK: %[[UNUSED:.*]] = arith.constant 0 : i32
584+
// CHECK: call @immutable_fn_return_void_with_unused_argument(%[[ARG0_MAIN]], %[[UNUSED]]) : (i32, i32) -> ()
585+
func.func @main2(%arg0: i32) -> () {
586+
%zero = arith.constant 0 : i32
587+
call @immutable_fn_return_void_with_unused_argument(%arg0, %zero) : (i32, i32) -> ()
588+
return
589+
}
572590
}
573591

574592
// -----

0 commit comments

Comments
 (0)