MLIR 24.0.0git
ModuleCombiner.cpp
Go to the documentation of this file.
1//===- ModuleCombiner.cpp - MLIR SPIR-V Module Combiner ---------*- C++ -*-===//
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// This file implements the SPIR-V module combiner library.
10//
11//===----------------------------------------------------------------------===//
12
14
16#include "mlir/IR/Attributes.h"
17#include "mlir/IR/Builders.h"
18#include "mlir/IR/SymbolTable.h"
19#include "llvm/ADT/Hashing.h"
20#include "llvm/ADT/STLExtras.h"
21#include "llvm/ADT/StringMap.h"
22
23using namespace mlir;
24
25static constexpr unsigned maxFreeID = 1 << 20;
26
27/// Returns an unused symbol in `module` for `oldSymbolName` by trying numeric
28/// suffix in `lastUsedID`.
29static StringAttr renameSymbol(StringRef oldSymName, unsigned &lastUsedID,
30 spirv::ModuleOp module) {
31 SmallString<64> newSymName(oldSymName);
32 newSymName.push_back('_');
33
34 MLIRContext *ctx = module->getContext();
35
36 while (lastUsedID < maxFreeID) {
37 auto possible = StringAttr::get(ctx, newSymName + Twine(++lastUsedID));
38 if (!SymbolTable::lookupSymbolIn(module, possible))
39 return possible;
40 }
41
42 return StringAttr::get(ctx, newSymName);
43}
44
45/// Checks if a symbol with the same name as `op` already exists in `source`.
46/// If so, renames `op` and updates all its references in `target`.
47static LogicalResult updateSymbolAndAllUses(SymbolOpInterface op,
48 spirv::ModuleOp target,
49 spirv::ModuleOp source,
50 unsigned &lastUsedID) {
51 if (!SymbolTable::lookupSymbolIn(source, op.getName()))
52 return success();
53
54 StringRef oldSymName = op.getName();
55 StringAttr newSymName = renameSymbol(oldSymName, lastUsedID, target);
56
57 if (failed(SymbolTable::replaceAllSymbolUses(op, newSymName, target)))
58 return op.emitError("unable to update all symbol uses for ")
59 << oldSymName << " to " << newSymName;
60
61 SymbolTable::setSymbolName(op, newSymName);
62 return success();
63}
64
65/// Computes a hash code to represent `symbolOp` based on all its attributes
66/// except for the symbol name.
67///
68/// Note: We use the operation's name (not the symbol name) as part of the hash
69/// computation. This prevents, for example, mistakenly considering a global
70/// variable and a spec constant as duplicates because their descriptor set +
71/// binding and spec_id, respectively, happen to hash to the same value.
72static llvm::hash_code computeHash(SymbolOpInterface symbolOp) {
73 NamedAttrList attrs(symbolOp->getDiscardableAttrDictionary());
74 symbolOp->getName().populateInherentAttrs(symbolOp, attrs);
75 auto range = llvm::make_filter_range(attrs, [](NamedAttribute attr) {
76 return attr.getName() != SymbolTable::getSymbolAttrName();
77 });
78
79 return llvm::hash_combine(symbolOp->getName(),
80 llvm::hash_combine_range(range));
81}
82
83namespace mlir {
84namespace spirv {
85
87 OpBuilder &combinedModuleBuilder,
88 SymbolRenameListener symRenameListener) {
89 if (inputModules.empty())
90 return nullptr;
91
92 spirv::ModuleOp firstModule = inputModules.front();
93 auto addressingModel = firstModule.getAddressingModel();
94 auto memoryModel = firstModule.getMemoryModel();
95 auto vceTriple = firstModule.getVceTriple();
96
97 // First check whether there are conflicts between addressing/memory model.
98 // Return early if so.
99 for (auto module : inputModules) {
100 if (module.getAddressingModel() != addressingModel ||
101 module.getMemoryModel() != memoryModel ||
102 module.getVceTriple() != vceTriple) {
103 module.emitError("input modules differ in addressing model, memory "
104 "model, and/or VCE triple");
105 return nullptr;
106 }
107 }
108
109 auto combinedModule =
110 spirv::ModuleOp::create(combinedModuleBuilder, firstModule.getLoc(),
111 addressingModel, memoryModel, vceTriple);
112 combinedModuleBuilder.setInsertionPointToStart(combinedModule.getBody());
113
114 // In some cases, a symbol in the (current state of the) combined module is
115 // renamed in order to enable the conflicting symbol in the input module
116 // being merged. For example, if the conflict is between a global variable in
117 // the current combined module and a function in the input module, the global
118 // variable is renamed. In order to notify listeners of the symbol updates in
119 // such cases, we need to keep track of the module from which the renamed
120 // symbol in the combined module originated. This map keeps such information.
121 llvm::StringMap<spirv::ModuleOp> symNameToModuleMap;
122
123 unsigned lastUsedID = 0;
124
125 for (auto inputModule : inputModules) {
126 OwningOpRef<spirv::ModuleOp> moduleClone = inputModule.clone();
127
128 // In the combined module, rename all symbols that conflict with symbols
129 // from the current input module. This renaming applies to all ops except
130 // for spirv.funcs. This way, if the conflicting op in the input module is
131 // non-spirv.func, we rename that symbol instead and maintain the spirv.func
132 // in the combined module name as it is.
133 for (auto &op : *combinedModule.getBody()) {
134 auto symbolOp = dyn_cast<SymbolOpInterface>(op);
135 if (!symbolOp)
136 continue;
137
138 StringRef oldSymName = symbolOp.getName();
139
140 if (!isa<FuncOp>(op) &&
141 failed(updateSymbolAndAllUses(symbolOp, combinedModule, *moduleClone,
142 lastUsedID)))
143 return nullptr;
144
145 StringRef newSymName = symbolOp.getName();
146
147 if (symRenameListener && oldSymName != newSymName) {
148 spirv::ModuleOp originalModule = symNameToModuleMap.lookup(oldSymName);
149
150 if (!originalModule) {
151 inputModule.emitError(
152 "unable to find original spirv::ModuleOp for symbol ")
153 << oldSymName;
154 return nullptr;
155 }
156
157 symRenameListener(originalModule, oldSymName, newSymName);
158
159 // Since the symbol name is updated, there is no need to maintain the
160 // entry that associates the old symbol name with the original module.
161 symNameToModuleMap.erase(oldSymName);
162 // Instead, add a new entry to map the new symbol name to the original
163 // module in case it gets renamed again later.
164 symNameToModuleMap[newSymName] = originalModule;
165 }
166 }
167
168 // In the current input module, rename all symbols that conflict with
169 // symbols from the combined module. This includes renaming spirv.funcs.
170 for (auto &op : *moduleClone->getBody()) {
171 auto symbolOp = dyn_cast<SymbolOpInterface>(op);
172 if (!symbolOp)
173 continue;
174
175 StringRef oldSymName = symbolOp.getName();
176
177 if (failed(updateSymbolAndAllUses(symbolOp, *moduleClone, combinedModule,
178 lastUsedID)))
179 return nullptr;
180
181 StringRef newSymName = symbolOp.getName();
182
183 if (symRenameListener) {
184 if (oldSymName != newSymName)
185 symRenameListener(inputModule, oldSymName, newSymName);
186
187 // Insert the module associated with the symbol name.
188 auto emplaceResult =
189 symNameToModuleMap.try_emplace(newSymName, inputModule);
190
191 // If an entry with the same symbol name is already present, this must
192 // be a problem with the implementation, specially clean-up of the map
193 // while iterating over the combined module above.
194 if (!emplaceResult.second) {
195 inputModule.emitError("did not expect to find an entry for symbol ")
196 << symbolOp.getName();
197 return nullptr;
198 }
199 }
200 }
201
202 // Clone all the module's ops to the combined module.
203 for (auto &op : *moduleClone->getBody())
204 combinedModuleBuilder.insert(op.clone());
205 }
206
207 // Deduplicate identical global variables, spec constants, and functions.
210
211 for (auto &op : *combinedModule.getBody()) {
212 SymbolOpInterface symbolOp = dyn_cast<SymbolOpInterface>(op);
213 if (!symbolOp)
214 continue;
215
216 // Do not support ops with operands or results.
217 // Global variables, spec constants, and functions won't have
218 // operands/results, but just for safety here.
219 if (op.getNumOperands() != 0 || op.getNumResults() != 0)
220 continue;
221
222 // Deduplicating functions are not supported yet.
223 if (isa<FuncOp>(op))
224 continue;
225
226 auto result = hashToSymbolOp.try_emplace(computeHash(symbolOp), symbolOp);
227 if (result.second)
228 continue;
229
230 SymbolOpInterface replacementSymOp = result.first->second;
231
233 symbolOp, replacementSymOp.getNameAttr(), combinedModule))) {
234 symbolOp.emitError("unable to update all symbol uses for ")
235 << symbolOp.getName() << " to " << replacementSymOp.getName();
236 return nullptr;
237 }
238
239 eraseList.push_back(symbolOp);
240 }
241
242 for (auto symbolOp : eraseList)
243 symbolOp.erase();
244
245 return combinedModule;
246}
247
248} // namespace spirv
249} // namespace mlir
return success()
static StringAttr renameSymbol(StringRef oldSymName, unsigned &lastUsedID, spirv::ModuleOp module)
Returns an unused symbol in module for oldSymbolName by trying numeric suffix in lastUsedID.
static LogicalResult updateSymbolAndAllUses(SymbolOpInterface op, spirv::ModuleOp target, spirv::ModuleOp source, unsigned &lastUsedID)
Checks if a symbol with the same name as op already exists in source.
static constexpr unsigned maxFreeID
static llvm::hash_code computeHash(SymbolOpInterface symbolOp)
Computes a hash code to represent symbolOp based on all its attributes except for the symbol name.
MLIRContext is the top-level object for a collection of MLIR operations.
Definition MLIRContext.h:63
NamedAttrList is array of NamedAttributes that tracks whether it is sorted and does some basic work t...
NamedAttribute represents a combination of a name and an Attribute value.
Definition Attributes.h:164
StringAttr getName() const
Return the name of the attribute.
This class helps build Operations.
Definition Builders.h:210
void setInsertionPointToStart(Block *block)
Sets the insertion point to the start of the specified block.
Definition Builders.h:434
Operation * insert(Operation *op)
Insert the given operation at the current insertion point and return it.
Definition Builders.cpp:430
This class acts as an owning reference to an op, and will automatically destroy the held op on destru...
Definition OwningOpRef.h:29
static StringRef getSymbolAttrName()
Return the name of the attribute used for symbol names.
Definition SymbolTable.h:76
static LogicalResult replaceAllSymbolUses(StringAttr oldSymbol, StringAttr newSymbol, Operation *from)
Attempt to replace all uses of the given symbol 'oldSymbol' with the provided symbol 'newSymbol' that...
static Operation * lookupSymbolIn(Operation *op, StringAttr symbol)
Returns the operation registered with the given symbol name with the regions of 'symbolTableOp'.
static void setSymbolName(Operation *symbol, StringAttr name)
Sets the name of the given symbol operation.
function_ref< void( spirv::ModuleOp originalModule, StringRef oldSymbol, StringRef newSymbol)> SymbolRenameListener
The listener function to receive symbol renaming events.
OwningOpRef< spirv::ModuleOp > combine(ArrayRef< spirv::ModuleOp > inputModules, OpBuilder &combinedModuleBuilder, SymbolRenameListener symRenameListener)
Combines a list of SPIR-V inputModules into one.
Include the generated interface declarations.
llvm::DenseMap< KeyT, ValueT, KeyInfoT, BucketT > DenseMap
Definition LLVM.h:120