CIRCT 24.0.0git
Loading...
Searching...
No Matches
LinkModules.cpp
Go to the documentation of this file.
1//===- OMLinkModules.cpp - OM Linker pass -----------------------*- 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// Contains the definitions of the OM Linker pass.
10//
11//===----------------------------------------------------------------------===//
12
16#include "mlir/IR/Block.h"
17#include "mlir/IR/Builders.h"
18#include "mlir/IR/BuiltinOps.h"
19#include "mlir/IR/SymbolTable.h"
20#include "mlir/IR/Threading.h"
21#include "mlir/Pass/Pass.h"
22#include "llvm/ADT/TypeSwitch.h"
23
24#include <memory>
25
26namespace circt {
27namespace om {
28#define GEN_PASS_DEF_LINKMODULES
29#include "circt/Dialect/OM/OMPasses.h.inc"
30} // namespace om
31} // namespace circt
32
33using namespace circt;
34using namespace om;
35using namespace mlir;
36
37namespace {
38// A map from a pair of enclosing module op and old symbol to a new symbol.
39using SymMappingTy =
40 llvm::DenseMap<std::pair<ModuleOp, StringAttr>, StringAttr>;
41
42struct ModuleInfo {
43 ModuleInfo(mlir::ModuleOp module) : module(module) {
44 block = std::make_unique<Block>();
45 }
46
47 // Populate `symbolToClasses`.
48 LogicalResult initialize();
49
50 // Update symbols based on the mapping and erase external classes.
51 void postProcess(const SymMappingTy &symMapping);
52
53 // A map from symbols to classes.
54 llvm::DenseMap<StringAttr, ClassLike> symbolToClasses;
55
56 // A target module.
57 ModuleOp module;
58
59 // A block to store original operations.
60 std::unique_ptr<Block> block;
61};
62
63struct LinkModulesPass
64 : public circt::om::impl::LinkModulesBase<LinkModulesPass> {
65 using Base::Base;
66 void runOnOperation() override;
67};
68
69} // namespace
70
71LogicalResult ModuleInfo::initialize() {
72 for (auto &op : llvm::make_early_inc_range(module.getOps())) {
73 if (auto classLike = dyn_cast<ClassLike>(op))
74 symbolToClasses.insert({classLike.getSymNameAttr(), classLike});
75 else {
76 // Keep the op.
77 op.moveBefore(block.get(), block->end());
78 }
79 }
80 return success();
81}
82
83void ModuleInfo::postProcess(const SymMappingTy &symMapping) {
84 AttrTypeReplacer replacer;
85 replacer.addReplacement(
86 // Update class types when their symbols were renamed.
87 [&](om::ClassType classType) -> std::pair<mlir::Type, WalkResult> {
88 auto it = symMapping.find({module, classType.getClassName().getAttr()});
89 // No change.
90 if (it == symMapping.end())
91 return {classType, WalkResult::skip()};
92 return {om::ClassType::get(classType.getContext(),
93 FlatSymbolRefAttr::get(it->second)),
94 WalkResult::skip()};
95 });
96
97 module.walk<WalkOrder::PreOrder>([&](Operation *op) {
98 // External modules must be erased.
99 if (isa<ClassExternOp>(op)) {
100 op->erase();
101 // ClassExternFieldOp will be deleted as well.
102 return WalkResult::skip();
103 }
104
105 llvm::TypeSwitch<Operation *>(op)
106 .Case<ClassOp>([&](ClassOp classOp) {
107 auto it = symMapping.find({module, classOp.getNameAttr()});
108 // Update its class name if changed.
109 if (it != symMapping.end())
110 classOp.setSymNameAttr(it->second);
111 })
112 .Case<ObjectOp, ElaboratedObjectOp>([&](auto objectLike) {
113 auto it = symMapping.find(
114 {module, objectLike.getClassNameAttr().getAttr()});
115 // Update its class name if changed.
116 if (it != symMapping.end())
117 objectLike.setClassNameAttr(FlatSymbolRefAttr::get(it->second));
118 });
119
120 // Update om.class types, including types nested in attributes.
121 replacer.replaceElementsIn(op,
122 /*replaceAttrs=*/true,
123 /*replaceLocs=*/false,
124 /*replaceTypes=*/true);
125 return WalkResult::advance();
126 });
127}
128
129// Return a failure if classes cannot be resolved. Return true if
130// it's necessary to rename symbols.
131static FailureOr<bool> resolveClasses(StringAttr name,
132 ArrayRef<ClassLike> classes) {
133 bool existExternalClass = false;
134 size_t countDefinition = 0;
135 ClassOp classOp;
136
137 for (auto op : classes) {
138 if (isa<ClassExternOp>(op))
139 existExternalClass = true;
140 else {
141 classOp = cast<ClassOp>(op);
142 ++countDefinition;
143 }
144 }
145
146 // There must be exactly one definition if the symbol was referred by an
147 // external class.
148 if (existExternalClass && countDefinition != 1) {
149 SmallVector<Location> classExternLocs;
150 SmallVector<Location> classLocs;
151 for (auto op : classes)
152 (isa<ClassExternOp>(op) ? classExternLocs : classLocs)
153 .push_back(op.getLoc());
154
155 auto diag = emitError(classExternLocs.front())
156 << "class " << name << " is declared as an external class but "
157 << (countDefinition == 0 ? "there is no definition"
158 : "there are multiple definitions");
159 for (auto loc : ArrayRef(classExternLocs).drop_front())
160 diag.attachNote(loc) << "class " << name << " is declared here as well";
161
162 if (countDefinition != 0) {
163 // There are multiple definitions.
164 for (auto loc : classLocs)
165 diag.attachNote(loc) << "class " << name << " is defined here";
166 }
167 return failure();
168 }
169
170 if (!existExternalClass)
171 return countDefinition != 1;
172
173 assert(classOp && countDefinition == 1);
174
175 // Raise errors if linked external modules are not compatible with the
176 // definition.
177 auto emitError = [&](Operation *op) {
178 auto diag = op->emitError()
179 << "failed to link class " << name
180 << " since declaration doesn't match the definition: ";
181 diag.attachNote(classOp.getLoc()) << "definition is here";
182 return diag;
183 };
184
185 for (auto op : classes) {
186 if (op == classOp)
187 continue;
188
189 if (classOp.getBodyBlock()->getNumArguments() !=
190 op.getBodyBlock()->getNumArguments())
191 return emitError(op) << "the number of arguments is not equal, "
192 << classOp.getBodyBlock()->getNumArguments()
193 << " vs " << op.getBodyBlock()->getNumArguments();
194 unsigned index = 0;
195 for (auto [l, r] : llvm::zip(classOp.getBodyBlock()->getArgumentTypes(),
196 op.getBodyBlock()->getArgumentTypes())) {
197 if (l != r)
198 return emitError(op) << index << "-th argument type is not equal, " << l
199 << " vs " << r;
200 index++;
201 }
202 // Check declared fields.
203 llvm::DenseSet<StringAttr> declaredFields;
204
205 for (auto nameAttr : op.getFieldNames()) {
206 StringAttr name = cast<StringAttr>(nameAttr);
207 std::optional<Type> opTypeOpt = op.getFieldType(name);
208
209 if (!opTypeOpt.has_value())
210 return emitError(op) << " no type for field " << name;
211 Type opType = opTypeOpt.value();
212
213 std::optional<Type> classTypeOpt = classOp.getFieldType(name);
214
215 // Field not found in its definition.
216 if (!classTypeOpt.has_value())
217 return emitError(op) << "declaration has a field " << name
218 << " but not found in its definition";
219 Type classType = classTypeOpt.value();
220
221 if (classType != opType)
222 return emitError(op)
223 << "declaration has a field " << name
224 << " but types don't match, " << classType << " vs " << opType;
225 declaredFields.insert(name);
226 }
227
228 for (auto fieldName : classOp.getFieldNames())
229 if (!declaredFields.count(cast<StringAttr>(fieldName)))
230 return emitError(op) << "definition has a field " << fieldName
231 << " but not found in this declaration";
232 }
233 return false;
234}
235
236void LinkModulesPass::runOnOperation() {
237 auto toplevelModule = getOperation();
238 // 1. Initialize ModuleInfo.
239 SmallVector<ModuleInfo> modules;
240 size_t counter = 0;
241 for (auto module : toplevelModule.getOps<ModuleOp>()) {
242 auto name = module->getAttrOfType<StringAttr>("om.namespace");
243 // Use `counter` if the namespace is not specified beforehand.
244 if (!name) {
245 name = StringAttr::get(module.getContext(), "module_" + Twine(counter++));
246 module->setAttr("om.namespace", name);
247 }
248 modules.emplace_back(module);
249 }
250
251 if (failed(failableParallelForEach(&getContext(), modules, [](auto &info) {
252 // Collect local information.
253 return info.initialize();
254 })))
255 return signalPassFailure();
256
257 // 2. Symbol resolution. Check that there is exactly single definition for
258 // public symbols and rename private symbols if necessary.
259
260 // Global namespace to get unique names to symbols.
261 Namespace nameSpace;
262 // A map from a pair of enclosing module op and old symbol to a new symbol.
263 SymMappingTy symMapping;
264
265 // Construct a global map from symbols to class operations.
266 llvm::MapVector<StringAttr, SmallVector<ClassLike>> symbolToClasses;
267 for (const auto &info : modules)
268 for (auto &[name, op] : info.symbolToClasses) {
269 symbolToClasses[name].push_back(op);
270 // Add names to avoid collision.
271 (void)nameSpace.newName(name.getValue());
272 }
273
274 // Resolve symbols. We consider a symbol used as an external module to be
275 // "public" thus we cannot rename such symbols when there is collision. We
276 // require a public symbol to have exactly one definition so otherwise raise
277 // an error.
278 for (auto &[name, classes] : symbolToClasses) {
279 // Check if it's legal to link classes. `resolveClasses` returns true if
280 // it's necessary to rename symbols.
281 auto result = resolveClasses(name, classes);
282 if (failed(result))
283 return signalPassFailure();
284
285 // We can resolve symbol collision for symbols not referred by external
286 // classes. Create a new name using `om.namespace` attributes as a
287 // suffix.
288 if (*result)
289 for (auto op : classes) {
290 auto enclosingModule = cast<mlir::ModuleOp>(op->getParentOp());
291 auto nameSpaceId =
292 enclosingModule->getAttrOfType<StringAttr>("om.namespace");
293 symMapping[{enclosingModule, name}] = StringAttr::get(
294 &getContext(),
295 nameSpace.newName(name.getValue(), nameSpaceId.getValue()));
296 }
297 }
298
299 // 3. Post-processing. Update class names and erase external classes.
300
301 // Rename private symbols and remove external classes.
302 parallelForEach(&getContext(), modules,
303 [&](auto &info) { info.postProcess(symMapping); });
304
305 // Finally move operations to the toplevel module.
306 auto *block = toplevelModule.getBody();
307 for (auto &info : modules) {
308 block->getOperations().splice(block->end(),
309 info.module.getBody()->getOperations());
310 // Restore non-OM operations.
311 assert(info.module.getBody()->empty());
312 info.module.getBody()->getOperations().splice(
313 info.module.getBody()->begin(), info.block->getOperations());
314 }
315}
assert(baseType &&"element must be base type")
static FailureOr< bool > resolveClasses(StringAttr name, ArrayRef< ClassLike > classes)
false return failure()
Parse an optional keyword or string and set instance into 'result'.
return success()
StringRef newName(const Twine &name)
Return a unique name, derived from the input name, and add the new name to the internal namespace.
Definition Namespace.h:86
void info(Twine message)
Definition LSPUtils.cpp:20
The InstanceGraph op interface, see InstanceGraphInterface.td for more details.
Definition om.py:1