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 // `populateInherentAttrs` adds the name property back to the list. Remove it
76 // so otherwise-identical symbols still hash equally after being renamed.
77 attrs.erase("sym_name");
78
79 return llvm::hash_combine(symbolOp->getName(),
80 llvm::hash_combine_range(attrs));
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...
Attribute erase(StringAttr name)
Erase the attribute with the given name from the list.
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 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