Skip to content

Commit abd1e1e

Browse files
committed
[mlir] Walk nested tables in symbol dce
The previous positioning was effectively that a symbol is dead if it cannot be addressed from top level. I think that is too strong a requirement: one can have operations that one cannot delete/DCE that refers to symbols which one could delete. This resulted in symbol-dce deleting symbols that are still referenced and the resulting IR being invalid. This instead all the symbols of top level operations of non-symbol table ops additionally, as those are either dead and DCE would have handled, or alive and we cannot just delete symbols referenced internally. E.g., this treats non-symbol table regioned ops more conservatively.
1 parent 7b074fc commit abd1e1e

File tree

2 files changed

+66
-2
lines changed

2 files changed

+66
-2
lines changed

mlir/lib/Transforms/SymbolDCE.cpp

Lines changed: 47 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ namespace mlir {
2222

2323
using namespace mlir;
2424

25+
#define DEBUG_TYPE "symbol-dce"
26+
2527
namespace {
2628
struct SymbolDCE : public impl::SymbolDCEBase<SymbolDCE> {
2729
void runOnOperation() override;
@@ -84,6 +86,8 @@ LogicalResult SymbolDCE::computeLiveness(Operation *symbolTableOp,
8486
SymbolTableCollection &symbolTable,
8587
bool symbolTableIsHidden,
8688
DenseSet<Operation *> &liveSymbols) {
89+
LLVM_DEBUG(llvm::dbgs() << "computeLiveness: " << symbolTableOp->getName()
90+
<< "\n");
8791
// A worklist of live operations to propagate uses from.
8892
SmallVector<Operation *, 16> worklist;
8993

@@ -108,15 +112,42 @@ LogicalResult SymbolDCE::computeLiveness(Operation *symbolTableOp,
108112
// that are referenced within.
109113
while (!worklist.empty()) {
110114
Operation *op = worklist.pop_back_val();
115+
LLVM_DEBUG(llvm::dbgs() << "processing: " << op->getName() << "\n");
111116

112117
// If this is a symbol table, recursively compute its liveness.
113118
if (op->hasTrait<OpTrait::SymbolTable>()) {
114119
// The internal symbol table is hidden if the parent is, if its not a
115120
// symbol, or if it is a private symbol.
116121
SymbolOpInterface symbol = dyn_cast<SymbolOpInterface>(op);
117122
bool symIsHidden = symbolTableIsHidden || !symbol || symbol.isPrivate();
123+
LLVM_DEBUG(llvm::dbgs() << "\tsymbol table: " << op->getName()
124+
<< " is hidden: " << symIsHidden << "\n");
118125
if (failed(computeLiveness(op, symbolTable, symIsHidden, liveSymbols)))
119126
return failure();
127+
} else {
128+
LLVM_DEBUG(llvm::dbgs()
129+
<< "\tnon-symbol table: " << op->getName() << " is hidden\n");
130+
// If the op is not a symbol table, then, unless op itself is dead which
131+
// would be handled by DCE, we need to check all the regions and blocks
132+
// within the op to find the uses (e.g., consider visibility within op as
133+
// if top level rather than relying on pure symbol table visibility). This
134+
// is more conservative than SymbolTable::walkSymbolTables in the case
135+
// where there is again SymbolTable information to take advantage of.
136+
for (auto &region : op->getRegions()) {
137+
for (auto &block : region.getBlocks()) {
138+
for (Operation &op : block) {
139+
SymbolOpInterface symbol = dyn_cast<SymbolOpInterface>(&op);
140+
if (!symbol) {
141+
worklist.push_back(&op);
142+
continue;
143+
}
144+
bool isDiscardable =
145+
symbol.isPrivate() && symbol.canDiscardOnUseEmpty();
146+
if (!isDiscardable && liveSymbols.insert(&op).second)
147+
worklist.push_back(&op);
148+
}
149+
}
150+
}
120151
}
121152

122153
// Collect the uses held by this operation.
@@ -128,13 +159,27 @@ LogicalResult SymbolDCE::computeLiveness(Operation *symbolTableOp,
128159
}
129160

130161
SmallVector<Operation *, 4> resolvedSymbols;
162+
// Get the first parent symbol table op.
163+
Operation *parentOp = op->getParentOp();
164+
while (parentOp && !parentOp->hasTrait<OpTrait::SymbolTable>()) {
165+
parentOp = parentOp->getParentOp();
166+
}
167+
assert(parentOp && "operation has no parent symbol table");
168+
169+
LLVM_DEBUG(llvm::dbgs() << "uses of " << op->getName() << "\n");
131170
for (const SymbolTable::SymbolUse &use : *uses) {
171+
LLVM_DEBUG(llvm::dbgs() << "\tuse: " << use.getUser() << "\n");
132172
// Lookup the symbols referenced by this use.
133173
resolvedSymbols.clear();
134-
if (failed(symbolTable.lookupSymbolIn(
135-
op->getParentOp(), use.getSymbolRef(), resolvedSymbols)))
174+
if (failed(symbolTable.lookupSymbolIn(parentOp, use.getSymbolRef(),
175+
resolvedSymbols)))
136176
// Ignore references to unknown symbols.
137177
continue;
178+
LLVM_DEBUG({
179+
llvm::dbgs() << "\t\tresolved symbols: ";
180+
llvm::interleaveComma(resolvedSymbols, llvm::dbgs());
181+
llvm::dbgs() << "\n";
182+
});
138183

139184
// Mark each of the resolved symbols as live.
140185
for (Operation *resolvedSymbol : resolvedSymbols)

mlir/test/Transforms/test-symbol-dce.mlir

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,3 +98,22 @@ module {
9898
// CHECK: "live.user"() {uses = [@unknown_symbol]} : () -> ()
9999
"live.user"() {uses = [@unknown_symbol]} : () -> ()
100100
}
101+
102+
// -----
103+
104+
// Check that we don't DCE nested symbols if they are used even if nested inside
105+
// an unnamed region.
106+
// CHECK-LABEL: module attributes {test.nested_unnamed_region}
107+
module attributes {test.nested_unnamed_region} {
108+
"test.one_region_op"() ({
109+
"test.symbol_scope"() ({
110+
// CHECK: func @nested_function
111+
func.func @nested_function() {
112+
return
113+
}
114+
func.call @nested_function() : () -> ()
115+
"test.finish"() : () -> ()
116+
}) : () -> ()
117+
"test.finish"() : () -> ()
118+
}) : () -> ()
119+
}

0 commit comments

Comments
 (0)