1 //===- SymbolTable.cpp - MLIR Symbol Table Class --------------------------===//
2 //
3 // Part of the MLIR 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 #include "mlir/IR/SymbolTable.h"
10 #include "llvm/ADT/SmallString.h"
11 
12 using namespace mlir;
13 
14 /// Return true if the given operation is unknown and may potentially define a
15 /// symbol table.
16 static bool isPotentiallyUnknownSymbolTable(Operation *op) {
17   return !op->getDialect() && op->getNumRegions() == 1;
18 }
19 
20 //===----------------------------------------------------------------------===//
21 // SymbolTable
22 //===----------------------------------------------------------------------===//
23 
24 /// Build a symbol table with the symbols within the given operation.
25 SymbolTable::SymbolTable(Operation *symbolTableOp)
26     : symbolTableOp(symbolTableOp) {
27   assert(symbolTableOp->hasTrait<OpTrait::SymbolTable>() &&
28          "expected operation to have SymbolTable trait");
29   assert(symbolTableOp->getNumRegions() == 1 &&
30          "expected operation to have a single region");
31   assert(has_single_element(symbolTableOp->getRegion(0)) &&
32          "expected operation to have a single block");
33 
34   for (auto &op : symbolTableOp->getRegion(0).front()) {
35     auto nameAttr = op.getAttrOfType<StringAttr>(getSymbolAttrName());
36     if (!nameAttr)
37       continue;
38 
39     auto inserted = symbolTable.insert({nameAttr.getValue(), &op});
40     (void)inserted;
41     assert(inserted.second &&
42            "expected region to contain uniquely named symbol operations");
43   }
44 }
45 
46 /// Look up a symbol with the specified name, returning null if no such name
47 /// exists. Names never include the @ on them.
48 Operation *SymbolTable::lookup(StringRef name) const {
49   return symbolTable.lookup(name);
50 }
51 
52 /// Erase the given symbol from the table.
53 void SymbolTable::erase(Operation *symbol) {
54   auto nameAttr = symbol->getAttrOfType<StringAttr>(getSymbolAttrName());
55   assert(nameAttr && "expected valid 'name' attribute");
56   assert(symbol->getParentOp() == symbolTableOp &&
57          "expected this operation to be inside of the operation with this "
58          "SymbolTable");
59 
60   auto it = symbolTable.find(nameAttr.getValue());
61   if (it != symbolTable.end() && it->second == symbol) {
62     symbolTable.erase(it);
63     symbol->erase();
64   }
65 }
66 
67 /// Insert a new symbol into the table and associated operation, and rename it
68 /// as necessary to avoid collisions.
69 void SymbolTable::insert(Operation *symbol, Block::iterator insertPt) {
70   auto nameAttr = symbol->getAttrOfType<StringAttr>(getSymbolAttrName());
71   assert(nameAttr && "expected valid 'name' attribute");
72 
73   auto &body = symbolTableOp->getRegion(0).front();
74   if (insertPt == Block::iterator() || insertPt == body.end())
75     insertPt = Block::iterator(body.getTerminator());
76 
77   assert(insertPt->getParentOp() == symbolTableOp &&
78          "expected insertPt to be in the associated module operation");
79 
80   body.getOperations().insert(insertPt, symbol);
81 
82   // Add this symbol to the symbol table, uniquing the name if a conflict is
83   // detected.
84   if (symbolTable.insert({nameAttr.getValue(), symbol}).second)
85     return;
86 
87   // If a conflict was detected, then the symbol will not have been added to
88   // the symbol table. Try suffixes until we get to a unique name that works.
89   SmallString<128> nameBuffer(nameAttr.getValue());
90   unsigned originalLength = nameBuffer.size();
91 
92   // Iteratively try suffixes until we find one that isn't used.
93   do {
94     nameBuffer.resize(originalLength);
95     nameBuffer += '_';
96     nameBuffer += std::to_string(uniquingCounter++);
97   } while (!symbolTable.insert({nameBuffer, symbol}).second);
98   symbol->setAttr(getSymbolAttrName(),
99                   StringAttr::get(nameBuffer, symbolTableOp->getContext()));
100 }
101 
102 /// Returns the operation registered with the given symbol name with the
103 /// regions of 'symbolTableOp'. 'symbolTableOp' is required to be an operation
104 /// with the 'OpTrait::SymbolTable' trait. Returns nullptr if no valid symbol
105 /// was found.
106 Operation *SymbolTable::lookupSymbolIn(Operation *symbolTableOp,
107                                        StringRef symbol) {
108   assert(symbolTableOp->hasTrait<OpTrait::SymbolTable>());
109 
110   // Look for a symbol with the given name.
111   for (auto &block : symbolTableOp->getRegion(0)) {
112     for (auto &op : block) {
113       auto nameAttr = op.template getAttrOfType<StringAttr>(
114           mlir::SymbolTable::getSymbolAttrName());
115       if (nameAttr && nameAttr.getValue() == symbol)
116         return &op;
117     }
118   }
119   return nullptr;
120 }
121 
122 /// Returns the operation registered with the given symbol name within the
123 /// closes parent operation with the 'OpTrait::SymbolTable' trait. Returns
124 /// nullptr if no valid symbol was found.
125 Operation *SymbolTable::lookupNearestSymbolFrom(Operation *from,
126                                                 StringRef symbol) {
127   assert(from && "expected valid operation");
128   while (!from->hasTrait<OpTrait::SymbolTable>()) {
129     from = from->getParentOp();
130 
131     // Check that this is a valid op and isn't an unknown symbol table.
132     if (!from || isPotentiallyUnknownSymbolTable(from))
133       return nullptr;
134   }
135   return lookupSymbolIn(from, symbol);
136 }
137 
138 //===----------------------------------------------------------------------===//
139 // SymbolTable Trait Types
140 //===----------------------------------------------------------------------===//
141 
142 LogicalResult OpTrait::impl::verifySymbolTable(Operation *op) {
143   if (op->getNumRegions() != 1)
144     return op->emitOpError()
145            << "Operations with a 'SymbolTable' must have exactly one region";
146   if (!has_single_element(op->getRegion(0)))
147     return op->emitOpError()
148            << "Operations with a 'SymbolTable' must have exactly one block";
149 
150   // Check that all symbols are uniquely named within child regions.
151   llvm::StringMap<Location> nameToOrigLoc;
152   for (auto &block : op->getRegion(0)) {
153     for (auto &op : block) {
154       // Check for a symbol name attribute.
155       auto nameAttr =
156           op.getAttrOfType<StringAttr>(mlir::SymbolTable::getSymbolAttrName());
157       if (!nameAttr)
158         continue;
159 
160       // Try to insert this symbol into the table.
161       auto it = nameToOrigLoc.try_emplace(nameAttr.getValue(), op.getLoc());
162       if (!it.second)
163         return op.emitError()
164             .append("redefinition of symbol named '", nameAttr.getValue(), "'")
165             .attachNote(it.first->second)
166             .append("see existing symbol definition here");
167     }
168   }
169   return success();
170 }
171 
172 LogicalResult OpTrait::impl::verifySymbol(Operation *op) {
173   if (!op->getAttrOfType<StringAttr>(mlir::SymbolTable::getSymbolAttrName()))
174     return op->emitOpError() << "requires string attribute '"
175                              << mlir::SymbolTable::getSymbolAttrName() << "'";
176   return success();
177 }
178 
179 //===----------------------------------------------------------------------===//
180 // Symbol Use Lists
181 //===----------------------------------------------------------------------===//
182 
183 /// Walk all of the symbol references within the given operation, invoking the
184 /// provided callback for each found use. The callbacks takes as arguments: the
185 /// use of the symbol, and the nested access chain to the attribute within the
186 /// operation dictionary. An access chain is a set of indices into nested
187 /// container attributes. For example, a symbol use in an attribute dictionary
188 /// that looks like the following:
189 ///
190 ///    {use = [{other_attr, @symbol}]}
191 ///
192 /// May have the following access chain:
193 ///
194 ///     [0, 0, 1]
195 ///
196 static WalkResult walkSymbolRefs(
197     Operation *op,
198     function_ref<WalkResult(SymbolTable::SymbolUse, ArrayRef<int>)> callback) {
199   // Check to see if the operation has any attributes.
200   DictionaryAttr attrDict = op->getAttrList().getDictionary();
201   if (!attrDict)
202     return WalkResult::advance();
203 
204   // A worklist of a container attribute and the current index into the held
205   // attribute list.
206   SmallVector<Attribute, 1> attrWorklist(1, attrDict);
207   SmallVector<int, 1> curAccessChain(1, /*Value=*/-1);
208 
209   // Process the symbol references within the given nested attribute range.
210   auto processAttrs = [&](int &index, auto attrRange) -> WalkResult {
211     for (Attribute attr : llvm::drop_begin(attrRange, index)) {
212       /// Check for a nested container attribute, these will also need to be
213       /// walked.
214       if (attr.isa<ArrayAttr>() || attr.isa<DictionaryAttr>()) {
215         attrWorklist.push_back(attr);
216         curAccessChain.push_back(-1);
217         return WalkResult::advance();
218       }
219 
220       // Invoke the provided callback if we find a symbol use and check for a
221       // requested interrupt.
222       if (auto symbolRef = attr.dyn_cast<SymbolRefAttr>())
223         if (callback({op, symbolRef}, curAccessChain).wasInterrupted())
224           return WalkResult::interrupt();
225 
226       // Make sure to keep the index counter in sync.
227       ++index;
228     }
229 
230     // Pop this container attribute from the worklist.
231     attrWorklist.pop_back();
232     curAccessChain.pop_back();
233     return WalkResult::advance();
234   };
235 
236   WalkResult result = WalkResult::advance();
237   do {
238     Attribute attr = attrWorklist.back();
239     int &index = curAccessChain.back();
240     ++index;
241 
242     // Process the given attribute, which is guaranteed to be a container.
243     if (auto dict = attr.dyn_cast<DictionaryAttr>())
244       result = processAttrs(index, make_second_range(dict.getValue()));
245     else
246       result = processAttrs(index, attr.cast<ArrayAttr>().getValue());
247   } while (!attrWorklist.empty() && !result.wasInterrupted());
248   return result;
249 }
250 
251 /// Walk all of the uses, for any symbol, that are nested within the given
252 /// operation 'from', invoking the provided callback for each. This does not
253 /// traverse into any nested symbol tables, and will also only return uses on
254 /// 'from' if it does not also define a symbol table.
255 static Optional<WalkResult> walkSymbolUses(
256     Operation *from,
257     function_ref<WalkResult(SymbolTable::SymbolUse, ArrayRef<int>)> callback) {
258   // If from is not a symbol table, check for uses. A symbol table defines a new
259   // scope, so we can't walk the attributes from the symbol table op.
260   if (!from->hasTrait<OpTrait::SymbolTable>()) {
261     if (walkSymbolRefs(from, callback).wasInterrupted())
262       return WalkResult::interrupt();
263   }
264 
265   SmallVector<Region *, 1> worklist;
266   worklist.reserve(from->getNumRegions());
267   for (Region &region : from->getRegions())
268     worklist.push_back(&region);
269 
270   while (!worklist.empty()) {
271     Region *region = worklist.pop_back_val();
272     for (Block &block : *region) {
273       for (Operation &op : block) {
274         if (walkSymbolRefs(&op, callback).wasInterrupted())
275           return WalkResult::interrupt();
276 
277         // If this operation has regions, and it as well as its dialect aren't
278         // registered then conservatively fail. The operation may define a
279         // symbol table, so we can't opaquely know if we should traverse to find
280         // nested uses.
281         if (isPotentiallyUnknownSymbolTable(&op))
282           return llvm::None;
283 
284         // If this op defines a new symbol table scope, we can't traverse. Any
285         // symbol references nested within 'op' are different semantically.
286         if (!op.hasTrait<OpTrait::SymbolTable>()) {
287           for (Region &region : op.getRegions())
288             worklist.push_back(&region);
289         }
290       }
291     }
292   }
293   return WalkResult::advance();
294 }
295 
296 /// Get an iterator range for all of the uses, for any symbol, that are nested
297 /// within the given operation 'from'. This does not traverse into any nested
298 /// symbol tables, and will also only return uses on 'from' if it does not
299 /// also define a symbol table. This is because we treat the region as the
300 /// boundary of the symbol table, and not the op itself. This function returns
301 /// None if there are any unknown operations that may potentially be symbol
302 /// tables.
303 auto SymbolTable::getSymbolUses(Operation *from) -> Optional<UseRange> {
304   std::vector<SymbolUse> uses;
305   Optional<WalkResult> result =
306       walkSymbolUses(from, [&](SymbolUse symbolUse, ArrayRef<int>) {
307         uses.push_back(symbolUse);
308         return WalkResult::advance();
309       });
310   return result ? Optional<UseRange>(std::move(uses)) : Optional<UseRange>();
311 }
312 
313 /// Get all of the uses of the given symbol that are nested within the given
314 /// operation 'from', invoking the provided callback for each. This does not
315 /// traverse into any nested symbol tables, and will also only return uses on
316 /// 'from' if it does not also define a symbol table. This is because we treat
317 /// the region as the boundary of the symbol table, and not the op itself. This
318 /// function returns None if there are any unknown operations that may
319 /// potentially be symbol tables.
320 auto SymbolTable::getSymbolUses(StringRef symbol, Operation *from)
321     -> Optional<UseRange> {
322   SymbolRefAttr symbolRefAttr = SymbolRefAttr::get(symbol, from->getContext());
323 
324   std::vector<SymbolUse> uses;
325   Optional<WalkResult> result =
326       walkSymbolUses(from, [&](SymbolUse symbolUse, ArrayRef<int>) {
327         if (symbolRefAttr == symbolUse.getSymbolRef())
328           uses.push_back(symbolUse);
329         return WalkResult::advance();
330       });
331   return result ? Optional<UseRange>(std::move(uses)) : Optional<UseRange>();
332 }
333 
334 /// Return if the given symbol is known to have no uses that are nested within
335 /// the given operation 'from'. This does not traverse into any nested symbol
336 /// tables, and will also only count uses on 'from' if it does not also define
337 /// a symbol table. This is because we treat the region as the boundary of the
338 /// symbol table, and not the op itself. This function will also return false if
339 /// there are any unknown operations that may potentially be symbol tables.
340 bool SymbolTable::symbolKnownUseEmpty(StringRef symbol, Operation *from) {
341   SymbolRefAttr symbolRefAttr = SymbolRefAttr::get(symbol, from->getContext());
342 
343   // Walk all of the symbol uses looking for a reference to 'symbol'.
344   Optional<WalkResult> walkResult =
345       walkSymbolUses(from, [&](SymbolUse symbolUse, ArrayRef<int>) {
346         return symbolUse.getSymbolRef() == symbolRefAttr
347                    ? WalkResult::interrupt()
348                    : WalkResult::advance();
349       });
350   return walkResult && !walkResult->wasInterrupted();
351 }
352 
353 /// Rebuild the given attribute container after replacing all references to a
354 /// symbol with `newSymAttr`.
355 static Attribute rebuildAttrAfterRAUW(Attribute container,
356                                       ArrayRef<SmallVector<int, 1>> accesses,
357                                       SymbolRefAttr newSymAttr,
358                                       unsigned depth) {
359   // Given a range of Attributes, update the ones referred to by the given
360   // access chains to point to the new symbol attribute.
361   auto updateAttrs = [&](auto &&attrRange) {
362     auto attrBegin = std::begin(attrRange);
363     for (unsigned i = 0, e = accesses.size(); i != e;) {
364       ArrayRef<int> access = accesses[i];
365       Attribute &attr = *std::next(attrBegin, access[depth]);
366 
367       // Check to see if this is a leaf access, i.e. a SymbolRef.
368       if (access.size() == depth + 1) {
369         attr = newSymAttr;
370         ++i;
371         continue;
372       }
373 
374       // Otherwise, this is a container. Collect all of the accesses for this
375       // index and recurse. The recursion here is bounded by the size of the
376       // largest access array.
377       auto nestedAccesses =
378           accesses.drop_front(i).take_while([&](ArrayRef<int> nextAccess) {
379             return nextAccess.size() > depth + 1 &&
380                    nextAccess[depth] == access[depth];
381           });
382       attr = rebuildAttrAfterRAUW(attr, nestedAccesses, newSymAttr, depth + 1);
383 
384       // Skip over all of the accesses that refer to the nested container.
385       i += nestedAccesses.size();
386     }
387   };
388 
389   if (auto dictAttr = container.dyn_cast<DictionaryAttr>()) {
390     auto newAttrs = llvm::to_vector<4>(dictAttr.getValue());
391     updateAttrs(make_second_range(newAttrs));
392     return DictionaryAttr::get(newAttrs, dictAttr.getContext());
393   }
394   auto newAttrs = llvm::to_vector<4>(container.cast<ArrayAttr>().getValue());
395   updateAttrs(newAttrs);
396   return ArrayAttr::get(newAttrs, container.getContext());
397 }
398 
399 /// Attempt to replace all uses of the given symbol 'oldSymbol' with the
400 /// provided symbol 'newSymbol' that are nested within the given operation
401 /// 'from'. This does not traverse into any nested symbol tables, and will
402 /// also only replace uses on 'from' if it does not also define a symbol
403 /// table. This is because we treat the region as the boundary of the symbol
404 /// table, and not the op itself. If there are any unknown operations that may
405 /// potentially be symbol tables, no uses are replaced and failure is returned.
406 LogicalResult SymbolTable::replaceAllSymbolUses(StringRef oldSymbol,
407                                                 StringRef newSymbol,
408                                                 Operation *from) {
409   SymbolRefAttr oldAttr = SymbolRefAttr::get(oldSymbol, from->getContext());
410   SymbolRefAttr newSymAttr = SymbolRefAttr::get(newSymbol, from->getContext());
411 
412   // A collection of operations along with their new attribute dictionary.
413   std::vector<std::pair<Operation *, DictionaryAttr>> updatedAttrDicts;
414 
415   // The current operation, and its old symbol access chains, being processed.
416   Operation *curOp = nullptr;
417   SmallVector<SmallVector<int, 1>, 1> accessChains;
418 
419   // Generate a new attribute dictionary for the current operation by replacing
420   // references to the old symbol.
421   auto generateNewAttrDict = [&] {
422     auto newAttrDict =
423         rebuildAttrAfterRAUW(curOp->getAttrList().getDictionary(), accessChains,
424                              newSymAttr, /*depth=*/0);
425     return newAttrDict.cast<DictionaryAttr>();
426   };
427 
428   // Walk the symbol uses collecting uses of the old symbol.
429   auto walkFn = [&](SymbolTable::SymbolUse symbolUse,
430                     ArrayRef<int> accessChain) {
431     if (symbolUse.getSymbolRef() != oldAttr)
432       return WalkResult::advance();
433 
434     // If there was a previous operation, generate a new attribute dict for it.
435     // This means that we've finished processing the current operation, so
436     // generate a new dictionary for it.
437     if (curOp && symbolUse.getUser() != curOp) {
438       updatedAttrDicts.push_back({curOp, generateNewAttrDict()});
439       accessChains.clear();
440     }
441 
442     // Record this access.
443     curOp = symbolUse.getUser();
444     accessChains.push_back(llvm::to_vector<1>(accessChain));
445     return WalkResult::advance();
446   };
447   if (!walkSymbolUses(from, walkFn))
448     return failure();
449 
450   // Update the attribute dictionaries as necessary.
451   for (auto &it : updatedAttrDicts)
452     it.first->setAttrs(it.second);
453 
454   // Check to see if we have a dangling op that needs to be processed.
455   if (curOp)
456     curOp->setAttrs(generateNewAttrDict());
457 
458   return success();
459 }
460