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 ®ion : from->getRegions()) 268 worklist.push_back(®ion); 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 ®ion : op.getRegions()) 288 worklist.push_back(®ion); 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