LLZK 3.0.0
An open-source IR for Zero Knowledge (ZK) circuits
Loading...
Searching...
No Matches
FlatteningPass.cpp
Go to the documentation of this file.
1//===-- LLZKFlatteningPass.cpp - Implements -llzk-flatten pass --*- C++ -*-===//
2//
3// Part of the LLZK Project, under the Apache License v2.0.
4// See LICENSE.txt for license information.
5// Copyright 2025 Veridise Inc.
6// SPDX-License-Identifier: Apache-2.0
7//
8//===----------------------------------------------------------------------===//
13//===----------------------------------------------------------------------===//
14
29#include "llzk/Util/Concepts.h"
30#include "llzk/Util/Debug.h"
35
36#include <mlir/Dialect/Affine/IR/AffineOps.h>
37#include <mlir/Dialect/Affine/LoopUtils.h>
38#include <mlir/Dialect/Arith/IR/Arith.h>
39#include <mlir/Dialect/SCF/IR/SCF.h>
40#include <mlir/Dialect/SCF/Utils/Utils.h>
41#include <mlir/Dialect/Utils/StaticValueUtils.h>
42#include <mlir/IR/Attributes.h>
43#include <mlir/IR/BuiltinAttributes.h>
44#include <mlir/IR/BuiltinOps.h>
45#include <mlir/IR/BuiltinTypes.h>
46#include <mlir/Interfaces/InferTypeOpInterface.h>
47#include <mlir/Pass/PassManager.h>
48#include <mlir/Support/LLVM.h>
49#include <mlir/Support/LogicalResult.h>
50#include <mlir/Transforms/DialectConversion.h>
51#include <mlir/Transforms/GreedyPatternRewriteDriver.h>
52#include <mlir/Transforms/WalkPatternRewriteDriver.h>
53
54#include <llvm/ADT/APInt.h>
55#include <llvm/ADT/DenseMap.h>
56#include <llvm/ADT/DepthFirstIterator.h>
57#include <llvm/ADT/STLExtras.h>
58#include <llvm/ADT/SmallVector.h>
59#include <llvm/ADT/TypeSwitch.h>
60#include <llvm/Support/Debug.h>
61
62#include <cstdint>
63
64// Include the generated base pass class definitions.
65namespace llzk::polymorphic {
66#define GEN_PASS_DEF_FLATTENINGPASS
68} // namespace llzk::polymorphic
69
70#include "SharedImpl.h"
71
72#define DEBUG_TYPE "llzk-flatten"
73
74using namespace mlir;
75using namespace llzk;
76using namespace llzk::array;
77using namespace llzk::component;
78using namespace llzk::constrain;
79using namespace llzk::felt;
80using namespace llzk::function;
81using namespace llzk::polymorphic;
82using namespace llzk::polymorphic::detail;
83
84namespace {
85
86static void reportDelayedDiagnostics(CallOp caller, SmallVector<Diagnostic> &&diagnostics) {
87 DiagnosticEngine &engine = caller.getContext()->getDiagEngine();
88 for (Diagnostic &diag : diagnostics) {
89 // Update any notes referencing an UnknownLoc to use the CallOp location.
90 for (Diagnostic &note : diag.getNotes()) {
91 assert(note.getNotes().empty() && "notes cannot have notes attached");
92 if (llvm::isa<UnknownLoc>(note.getLocation())) {
93 note = std::move(Diagnostic(caller.getLoc(), note.getSeverity()).append(note.str()));
94 }
95 }
96 // Report. Based on InFlightDiagnostic::report().
97 engine.emit(std::move(diag));
98 }
99}
100
101class ConversionTracker {
103 bool modified;
106 DenseMap<StructType, StructType> structInstantiations;
108 DenseMap<StructType, StructType> reverseInstantiations;
110 DenseSet<SymbolRefAttr> funcInstantiations;
113 DenseMap<StructType, SmallVector<Diagnostic>> delayedDiagnostics;
114
115public:
116 bool isModified() const { return modified; }
117 void resetModifiedFlag() { modified = false; }
118 void updateModifiedFlag(bool currStepModified) { modified |= currStepModified; }
119
120 void recordInstantiation(StructType oldType, StructType newType) {
121 assert(!isNullOrEmpty(oldType.getParams()) && "cannot instantiate with no params");
122
123 auto forwardResult = structInstantiations.try_emplace(oldType, newType);
124 if (forwardResult.second) {
125 // Insertion was successful
126 // ASSERT: The reverse map does not contain this mapping either
127 assert(!reverseInstantiations.contains(newType));
128 reverseInstantiations[newType] = oldType;
129 // Set the modified flag
130 modified = true;
131 } else {
132 // ASSERT: If a mapping already existed for `oldType` it must be `newType`
133 assert(forwardResult.first->getSecond() == newType);
134 // ASSERT: The reverse mapping is already present as well
135 assert(reverseInstantiations.lookup(newType) == oldType);
137 assert(structInstantiations.size() == reverseInstantiations.size());
138 }
141 std::optional<StructType> getInstantiation(StructType oldType) const {
142 auto cachedResult = structInstantiations.find(oldType);
143 if (cachedResult != structInstantiations.end()) {
144 return cachedResult->second;
145 }
146 return std::nullopt;
148
150 void recordInstantiation(SymbolRefAttr funcName) {
151 funcInstantiations.insert(funcName);
152 modified = true;
153 }
154
155 /// Collect the fully-qualified names of all structs and free functions that were instantiated.
156 DenseSet<SymbolRefAttr> getInstantiatedDefinitionNames() const {
157 DenseSet<SymbolRefAttr> instantiatedNames = funcInstantiations;
158 for (const auto &[origRemoteTy, _] : structInstantiations) {
159 instantiatedNames.insert(origRemoteTy.getNameRef());
161 return instantiatedNames;
162 }
163
164 void reportDelayedDiagnostics(StructType newType, CallOp caller) {
165 auto res = delayedDiagnostics.find(newType);
166 if (res != delayedDiagnostics.end()) {
167 ::reportDelayedDiagnostics(caller, std::move(res->second));
168
169 // Emitting a Diagnostic consumes it (per DiagnosticEngine::emit) so remove them from the map.
170 // Unfortunately, this means if the key StructType is the result of instantiation at multiple
171 // `compute()` calls it will only be reported at one of those locations, not all.
172 delayedDiagnostics.erase(newType);
173 }
174 }
175
176 SmallVector<Diagnostic> &delayedDiagnosticSet(StructType newType) {
177 return delayedDiagnostics[newType];
178 }
179
181 /// than the old type with additional allowance for the results of struct flattening conversions.
182 bool isLegalConversion(Type oldType, Type newType, const char *patName) const {
183 std::function<bool(Type, Type)> checkInstantiations = [&](Type oTy, Type nTy) {
184 // Check if `oTy` is a struct with a known instantiation to `nTy`
185 if (StructType oldStructType = llvm::dyn_cast<StructType>(oTy)) {
186 // Note: The values in `structInstantiations` must be no-parameter struct types
187 // so there is no need for recursive check, simple equality is sufficient.
188 if (this->structInstantiations.lookup(oldStructType) == nTy) {
189 return true;
190 }
191 }
192 // Check if `nTy` is the result of a struct instantiation and if the pre-image of
193 // that instantiation (i.e., the parameterized version of the instantiated struct)
194 // is a more concrete unification of `oTy`.
195 if (StructType newStructType = llvm::dyn_cast<StructType>(nTy)) {
196 if (auto preImage = this->reverseInstantiations.lookup(newStructType)) {
197 if (isMoreConcreteUnification(oTy, preImage, checkInstantiations)) {
198 return true;
199 }
200 }
201 }
202 return false;
203 };
204
205 if (isMoreConcreteUnification(oldType, newType, checkInstantiations)) {
206 return true;
207 }
208 LLVM_DEBUG(
209 llvm::dbgs() << "[" << patName << "] Cannot replace old type " << oldType
210 << " with new type " << newType
211 << " because it does not define a compatible and more concrete type.\n";
212 );
213 return false;
214 }
215
216 template <typename T, typename U>
217 inline bool areLegalConversions(T oldTypes, U newTypes, const char *patName) const {
218 return llvm::all_of(
219 llvm::zip_equal(oldTypes, newTypes), [this, &patName](std::tuple<Type, Type> oldThenNew) {
220 return this->isLegalConversion(std::get<0>(oldThenNew), std::get<1>(oldThenNew), patName);
221 }
222 );
223 }
224};
225
226template <typename Impl, typename Op, typename... HandledAttrs>
227class SymbolUserHelper : public OpConversionPattern<Op> {
228private:
229 const DenseMap<Attribute, Attribute> &paramNameToValue;
230
231 SymbolUserHelper(
232 TypeConverter &converter, MLIRContext *ctx, unsigned patternBenefit,
233 const DenseMap<Attribute, Attribute> &paramNameToInstantiatedValue
234 )
235 : OpConversionPattern<Op>(converter, ctx, patternBenefit),
236 paramNameToValue(paramNameToInstantiatedValue) {}
237
238public:
239 using OpAdaptor = typename mlir::OpConversionPattern<Op>::OpAdaptor;
240
241 virtual Attribute getNameAttr(Op) const = 0;
242
243 virtual LogicalResult handleDefaultRewrite(
244 Attribute, Op op, OpAdaptor, ConversionPatternRewriter &, Attribute a
245 ) const {
246 return op->emitOpError().append("expected value with type ", op.getType(), " but found ", a);
247 }
248
249 LogicalResult
250 matchAndRewrite(Op op, OpAdaptor adaptor, ConversionPatternRewriter &rewriter) const override {
251 LLVM_DEBUG(llvm::dbgs() << "[SymbolUserHelper] op: " << op << '\n');
252 auto res = this->paramNameToValue.find(getNameAttr(op));
253 if (res == this->paramNameToValue.end()) {
254 LLVM_DEBUG(llvm::dbgs() << "[SymbolUserHelper] no instantiation for " << op << '\n');
255 return failure();
256 }
257 llvm::TypeSwitch<Attribute, LogicalResult> TS(res->second);
258 llvm::TypeSwitch<Attribute, LogicalResult> *ptr = &TS;
259
260 ((ptr = &(ptr->template Case<HandledAttrs>([&](HandledAttrs a) {
261 return static_cast<const Impl *>(this)->handleRewrite(res->first, op, adaptor, rewriter, a);
262 }))),
263 ...);
264
265 return TS.Default([&](Attribute a) {
266 return handleDefaultRewrite(res->first, op, adaptor, rewriter, a);
267 });
268 }
269 friend Impl;
270};
271
272class ClonedBodyConstReadOpPattern
273 : public SymbolUserHelper<
274 ClonedBodyConstReadOpPattern, ConstReadOp, IntegerAttr, FeltConstAttr> {
275 SmallVector<Diagnostic> &diagnostics;
276
277 using super =
278 SymbolUserHelper<ClonedBodyConstReadOpPattern, ConstReadOp, IntegerAttr, FeltConstAttr>;
279
280public:
281 ClonedBodyConstReadOpPattern(
282 TypeConverter &converter, MLIRContext *ctx,
283 const DenseMap<Attribute, Attribute> &paramNameToInstantiatedValue,
284 SmallVector<Diagnostic> &instantiationDiagnostics
285 )
286 // benefit>0 so this applies instead of GeneralTypeReplacePattern<ConstReadOp>
287 : super(converter, ctx, /*patternBenefit=*/1, paramNameToInstantiatedValue),
288 diagnostics(instantiationDiagnostics) {}
289
290 Attribute getNameAttr(ConstReadOp op) const override { return op.getConstNameAttr(); }
291
292 LogicalResult handleRewrite(
293 Attribute sym, ConstReadOp op, OpAdaptor, ConversionPatternRewriter &rewriter, IntegerAttr a
294 ) const {
295 APInt attrValue = a.getValue();
296 Type origResTy = op.getType();
297 Type newResTy = getTypeConverter()->convertType(origResTy);
298 if (!newResTy) {
299 return op->emitOpError().append("could not convert result type ", origResTy);
300 }
301
302 if (FeltType ty = llvm::dyn_cast<FeltType>(newResTy)) {
304 rewriter, op, FeltConstAttr::get(getContext(), attrValue, ty)
305 );
306 return success();
307 }
308
309 if (llvm::isa<IndexType>(newResTy)) {
311 return success();
312 }
313
314 if (newResTy.isSignlessInteger(1)) {
315 // Treat 0 as false and any other value as true (but give a warning if it's not 1)
316 if (attrValue.isZero()) {
317 replaceOpWithNewOp<arith::ConstantIntOp>(rewriter, op, false, newResTy);
318 return success();
319 }
320 if (!attrValue.isOne()) {
321 Location opLoc = op.getLoc();
322 Diagnostic diag(opLoc, DiagnosticSeverity::Warning);
323 diag << "Interpreting non-zero value " << stringWithoutType(a) << " as true";
324 if (getContext()->shouldPrintOpOnDiagnostic()) {
325 diag.attachNote(opLoc) << "see current operation: " << *op;
326 }
327 diag.attachNote(UnknownLoc::get(getContext()))
328 << "when instantiating '" << StructDefOp::getOperationName() << "' parameter \"" << sym
329 << "\" for this call";
330 diagnostics.push_back(std::move(diag));
331 }
332 replaceOpWithNewOp<arith::ConstantIntOp>(rewriter, op, true, newResTy);
333 return success();
334 }
335 return op->emitOpError().append("unexpected result type ", newResTy);
336 }
337
338 LogicalResult handleRewrite(
339 Attribute, ConstReadOp op, OpAdaptor, ConversionPatternRewriter &rewriter, FeltConstAttr a
340 ) const {
341 replaceOpWithNewOp<FeltConstantOp>(rewriter, op, a);
342 return success();
343 }
344};
345
348struct MatchFailureListener : public RewriterBase::Listener {
349 bool hadFailure = false;
350
351 ~MatchFailureListener() override {}
352
353 void notifyMatchFailure(Location loc, function_ref<void(Diagnostic &)> reasonCallback) override {
354 hadFailure = true;
355
356 InFlightDiagnostic diag = emitError(loc);
357 reasonCallback(*diag.getUnderlyingDiagnostic());
358 diag.report();
359 }
360};
361
362static LogicalResult
363applyAndFoldGreedily(ModuleOp modOp, ConversionTracker &tracker, RewritePatternSet &&patterns) {
364 bool currStepModified = false;
365 MatchFailureListener failureListener;
366 LogicalResult result = applyPatternsGreedily(
367 modOp->getRegion(0), std::move(patterns),
368 GreedyRewriteConfig {.maxIterations = 20, .listener = &failureListener, .fold = true},
369 &currStepModified
370 );
371 tracker.updateModifiedFlag(currStepModified);
372 return failure(result.failed() || failureListener.hadFailure);
373}
374
376template <bool AllowStructParams = true> bool isConcreteAttr(Attribute a) {
377 return classifyAttrConcreteness(a, AllowStructParams) == AttrConcreteness::Concrete;
378}
379
380static SymbolRefAttr
381convertCalleeSymRefs(SymbolRefAttr callee, const DenseMap<Attribute, Attribute> &paramNameToValue) {
382 auto it = paramNameToValue.find(FlatSymbolRefAttr::get(callee.getRootReference()));
383 if (it == paramNameToValue.end()) {
384 return callee;
385 }
386
387 auto tyAttr = llvm::dyn_cast<TypeAttr>(it->second);
388 if (!tyAttr) {
389 return callee;
390 }
391
392 auto structTy = llvm::dyn_cast<StructType>(tyAttr.getValue());
393 if (!structTy) {
394 return callee;
395 }
396
397 SmallVector<FlatSymbolRefAttr> newPieces = getPieces(structTy.getNameRef());
398 llvm::append_range(newPieces, callee.getNestedReferences());
399 return asSymbolRefAttr(newPieces);
400}
401
402static void
403convertCalleesInPlace(Operation *op, const DenseMap<Attribute, Attribute> &paramNameToValue) {
404 op->walk([&paramNameToValue](CallOp callOp) {
405 callOp.setCalleeAttr(convertCalleeSymRefs(callOp.getCalleeAttr(), paramNameToValue));
406 });
407}
408
409static bool calleeReferencesTemplateParam(CallOp op) {
410 SymbolRefAttr callee = op.getCalleeAttr();
411 if (!callee || callee.getNestedReferences().size() != 1) {
412 return false;
413 }
414 TemplateOp parentTemplate = getParentOfType<TemplateOp>(op);
415 if (!parentTemplate) {
416 return false;
417 }
418 return parentTemplate.hasConstNamed<TemplateParamOp>(callee.getRootReference());
419}
420
425static std::optional<Attribute>
426evaluateExpr(TemplateExprOp exprOp, const DenseMap<Attribute, Attribute> &paramNameToConcrete) {
427 // Map from SSA value in the expr body to its concrete Attribute.
428 DenseMap<Value, Attribute> valueMap;
429 for (Operation &bodyOp : exprOp.getInitializerRegion().front()) {
430 if (auto yieldOp = llvm::dyn_cast<YieldOp>(bodyOp)) {
431 auto it = valueMap.find(yieldOp.getVal());
432 return it != valueMap.end() ? std::make_optional(it->second) : std::nullopt;
433 }
434
435 if (auto constReadOp = llvm::dyn_cast<ConstReadOp>(bodyOp)) {
436 auto it = paramNameToConcrete.find(constReadOp.getConstNameAttr());
437 if (it == paramNameToConcrete.end()) {
438 return std::nullopt; // a referenced param is not concrete
439 }
440 // If the attribute type is `FeltType` but it's stored as an IntegerAttr, promote to
441 // a `FeltConstAttr`.
442 Attribute val = it->second;
443 if (auto intAttr = llvm::dyn_cast<IntegerAttr>(val)) {
444 if (auto feltTy = llvm::dyn_cast<FeltType>(constReadOp.getResult().getType())) {
445 val = FeltConstAttr::get(bodyOp.getContext(), intAttr.getValue(), feltTy);
446 }
447 }
448 valueMap[constReadOp.getResult()] = val;
449 continue;
450 }
451
452 // Gather constant attributes for all operands.
453 SmallVector<Attribute> operandAttrs;
454 operandAttrs.reserve(bodyOp.getNumOperands());
455 for (Value operand : bodyOp.getOperands()) {
456 auto it = valueMap.find(operand);
457 if (it == valueMap.end()) {
458 return std::nullopt; // operand not known as a constant
459 }
460 operandAttrs.push_back(it->second);
461 }
462
463 // Try constant folding.
464 SmallVector<OpFoldResult> foldResults;
465 if (succeeded(bodyOp.fold(operandAttrs, foldResults)) &&
466 foldResults.size() == bodyOp.getNumResults()) {
467 for (auto [result, fr] : llvm::zip_equal(bodyOp.getResults(), foldResults)) {
468 if (Attribute a = llvm::dyn_cast<Attribute>(fr)) {
469 valueMap[result] = a;
470 } else {
471 return std::nullopt;
472 }
473 }
474 }
475 }
476 return std::nullopt; // no YieldOp found (shouldn't happen in a valid expr)
477}
478
482static void
483evaluateTemplateExprs(TemplateOp templateOp, DenseMap<Attribute, Attribute> &paramNameToConcrete) {
484 LLVM_DEBUG(
485 llvm::dbgs() << "[evaluateTemplateExprs] before: " << debug::toStringList(paramNameToConcrete)
486 << '\n'
487 );
488 for (TemplateExprOp exprOp : templateOp.getConstOps<TemplateExprOp>()) {
489 std::optional<Attribute> result = evaluateExpr(exprOp, paramNameToConcrete);
490 if (result.has_value()) {
491 auto exprNameAttr = FlatSymbolRefAttr::get(exprOp.getSymNameAttr());
492 paramNameToConcrete.try_emplace(exprNameAttr, *result);
493 LLVM_DEBUG(
494 llvm::dbgs() << "[evaluateTemplateExprs] expr @" << exprOp.getSymName()
495 << " evaluated to " << *result << '\n'
496 );
497 }
498 }
499 LLVM_DEBUG(
500 llvm::dbgs() << "[evaluateTemplateExprs] after: " << debug::toStringList(paramNameToConcrete)
501 << '\n'
502 );
503}
504
505static inline bool tableOffsetIsntSymbol(MemberReadOp op) {
506 return !llvm::isa_and_present<SymbolRefAttr>(op.getTableOffset().value_or(nullptr));
507}
508
511class ClonedMemberReadOpPattern
512 : public SymbolUserHelper<ClonedMemberReadOpPattern, MemberReadOp, IntegerAttr> {
513 using super = SymbolUserHelper<ClonedMemberReadOpPattern, MemberReadOp, IntegerAttr>;
514
515public:
516 ClonedMemberReadOpPattern(
517 TypeConverter &converter, MLIRContext *ctx,
518 const DenseMap<Attribute, Attribute> &paramNameToInstantiatedValue
519 )
520 // benefit>0 so this applies instead of GeneralTypeReplacePattern<MemberReadOp>
521 : super(converter, ctx, /*patternBenefit=*/1, paramNameToInstantiatedValue) {}
522
523 Attribute getNameAttr(MemberReadOp op) const override {
524 return op.getTableOffset().value_or(nullptr);
525 }
526
527 LogicalResult handleRewrite(
528 Attribute, MemberReadOp op, OpAdaptor, ConversionPatternRewriter &rewriter, IntegerAttr a
529 ) const {
530 rewriter.modifyOpInPlace(op, [&]() {
531 op.setTableOffsetAttr(rewriter.getIndexAttr(fromAPInt(a.getValue())));
532 });
533
534 return success();
535 }
536
537 LogicalResult handleDefaultRewrite(
538 Attribute, MemberReadOp op, OpAdaptor, ConversionPatternRewriter &, Attribute a
539 ) const override {
540 return op->emitOpError().append(
541 "table offset requires an integer template value, but found ", a
542 );
543 }
544
545 LogicalResult matchAndRewrite(
546 MemberReadOp op, OpAdaptor adaptor, ConversionPatternRewriter &rewriter
547 ) const override {
548 LLVM_DEBUG(llvm::dbgs() << "[ClonedMemberReadOpPattern] MemberReadOp: " << op << '\n';);
549 if (tableOffsetIsntSymbol(op)) {
550 return failure();
551 }
552
553 return super::matchAndRewrite(op, adaptor, rewriter);
554 }
555};
556
558
561class StructCloner {
562 ConversionTracker &tracker_;
563 ModuleOp rootMod;
564 SymbolTableCollection symTables;
565 bool reportMissing = true;
566
567 class MappedTypeConverter : public TypeConverter {
568 StructType origTy;
569 StructType newTy;
570 const DenseMap<Attribute, Attribute> &paramNameToValue;
571
572 inline Attribute convertIfPossible(Attribute a) const {
573 auto res = this->paramNameToValue.find(a);
574 return (res != this->paramNameToValue.end()) ? res->second : a;
575 }
576
577 public:
578 MappedTypeConverter(
579 StructType originalType, StructType newType,
581 const DenseMap<Attribute, Attribute> &paramNameToInstantiatedValue
582 )
583 : TypeConverter(), origTy(originalType), newTy(newType),
584 paramNameToValue(paramNameToInstantiatedValue) {
585
586 addConversion([](Type inputTy) { return inputTy; });
587
588 addConversion([this](StructType inputTy) {
589 LLVM_DEBUG(llvm::dbgs() << "[MappedTypeConverter] convert " << inputTy << '\n');
590
591 // Check for replacement of the full type
592 if (inputTy == this->origTy) {
593 return this->newTy;
594 }
595 // Check for replacement of parameter symbol names with concrete values
596 if (ArrayAttr inputTyParams = inputTy.getParams()) {
597 SmallVector<Attribute> updated;
598 for (Attribute a : inputTyParams) {
599 if (TypeAttr ta = dyn_cast<TypeAttr>(a)) {
600 updated.push_back(TypeAttr::get(this->convertType(ta.getValue())));
601 } else {
602 updated.push_back(convertIfPossible(a));
603 }
604 }
605 return getStructTypeWithParams(inputTy.getNameRef(), inputTy.getContext(), updated);
606 }
607 // Otherwise, return the type unchanged
608 return inputTy;
609 });
610
611 addConversion([this](ArrayType inputTy) {
612 // Check for replacement of parameter symbol names with concrete values
613 ArrayRef<Attribute> dimSizes = inputTy.getDimensionSizes();
614 if (!dimSizes.empty()) {
615 SmallVector<Attribute> updated;
616 for (Attribute a : dimSizes) {
617 updated.push_back(convertIfPossible(a));
618 }
619 return ArrayType::get(this->convertType(inputTy.getElementType()), updated);
620 }
621 // Otherwise, return the type unchanged
622 return inputTy;
623 });
624
625 addConversion([this](TypeVarType inputTy) -> Type {
626 // Check for replacement of parameter symbol name with a concrete type
627 if (TypeAttr tyAttr = llvm::dyn_cast<TypeAttr>(convertIfPossible(inputTy.getNameRef()))) {
628 Type convertedType = tyAttr.getValue();
629 // Use the new type unless it contains a TypeVarType because a TypeVarType from a
630 // different struct references a parameter name from that other struct, not from the
631 // current struct so the reference would be invalid.
632 if (isConcreteType(convertedType)) {
633 return convertedType;
634 }
635 }
636 return inputTy;
637 });
638 }
639 };
640
641 FailureOr<StructType> genClone(StructType typeAtCaller, ArrayRef<Attribute> typeAtCallerParams) {
642 LLVM_DEBUG(llvm::dbgs() << "[StructCloner] attempting clone of " << typeAtCaller << '\n');
643 // Find the StructDefOp for the original StructType
644 FailureOr<SymbolLookupResult<StructDefOp>> r =
645 typeAtCaller.getDefinition(symTables, rootMod, reportMissing);
646 if (failed(r)) {
647 LLVM_DEBUG(llvm::dbgs() << "[StructCloner] skip: cannot find StructDefOp \n");
648 return failure(); // getDefinition() already emits a sufficient error message
649 }
650 LLVM_DEBUG(llvm::dbgs() << "[StructCloner] found definition\n";);
651
652 StructDefOp origStruct = r->get();
653 StructType typeAtDef = origStruct.getType();
654 MLIRContext *ctx = origStruct.getContext();
655
656 // Map of StructDefOp parameter name to concrete Attribute at the current instantiation site.
657 DenseMap<Attribute, Attribute> paramNameToConcrete;
658 // List of concrete Attributes from the struct instantiation with `nullptr` at any positions
659 // where the original attribute from the current instantiation site was not concrete. This is
660 // used for generating the new struct name. See `BuildShortTypeString::from()`.
661 SmallVector<Attribute> attrsForInstantiatedNameSuffix;
662 // List of template const param names that must be preserved because they
663 // were not assigned concrete values at the current instantiation site.
664 SmallVector<Attribute> remainingNames;
665 // Reduced from `typeAtCallerParams` to contain only the non-concrete Attributes.
666 ArrayAttr reducedCallerParams = nullptr;
667 {
668 ArrayAttr paramNames = typeAtDef.getParams();
669
670 // pre-conditions
671 assert(!isNullOrEmpty(paramNames));
672 assert(paramNames.size() == typeAtCallerParams.size());
673
674 SmallVector<Attribute> nonConcreteParams;
675 for (size_t i = 0, e = paramNames.size(); i < e; ++i) {
676 Attribute next = typeAtCallerParams[i];
677 if (isConcreteAttr<false>(next)) {
678 paramNameToConcrete[paramNames[i]] = next;
679 attrsForInstantiatedNameSuffix.push_back(next);
680 } else {
681 remainingNames.push_back(paramNames[i]);
682 nonConcreteParams.push_back(next);
683 attrsForInstantiatedNameSuffix.push_back(nullptr);
684 }
685 }
686 // post-conditions
687 assert(remainingNames.size() == nonConcreteParams.size());
688 assert(attrsForInstantiatedNameSuffix.size() == paramNames.size());
689 assert(remainingNames.size() + paramNameToConcrete.size() == paramNames.size());
690
691 if (paramNameToConcrete.empty()) {
692 LLVM_DEBUG(llvm::dbgs() << "[StructCloner] skip: no concrete params \n");
693 return failure();
694 }
695 if (!remainingNames.empty()) {
696 reducedCallerParams = ArrayAttr::get(ctx, nonConcreteParams);
697 }
698 }
699
700 // This list will be used to build the new remote/external type.
701 SmallVector<FlatSymbolRefAttr> typeAtCallerSymPieces = getPieces(typeAtCaller.getNameRef());
702 typeAtCallerSymPieces.pop_back(); // drop struct name
703 // Name of template with instantiated parameter values.
704 std::string templateNameWithAttrs = BuildShortTypeString::from(
705 typeAtCallerSymPieces.back().getValue().str(), attrsForInstantiatedNameSuffix
706 );
707
708 // Get parent refs
709 TemplateOp parentTemplate = getParentOfType<TemplateOp>(origStruct);
710 assert(parentTemplate && "parameterized struct must be nested in a TemplateOp");
711 ModuleOp parentModule = getParentOfType<ModuleOp>(parentTemplate);
712 assert(parentModule && "TemplateOp must be nested in a ModuleOp");
713
714 // Evaluate any poly.expr symbols whose param dependencies are now concrete; add them to the
715 // map so ClonedBodyConstReadOpPattern can replace uses of those symbols too.
716 evaluateTemplateExprs(parentTemplate, paramNameToConcrete);
717
718 // Clone the original struct.
719 StructDefOp newStruct = origStruct.clone();
720 convertCalleesInPlace(newStruct, paramNameToConcrete);
721 if (remainingNames.empty()) { // FULL INSTANTIATION CASE
722 // Set name of the new struct by prepending its name with instantiated template name.
723 newStruct.setSymName(
724 (templateNameWithAttrs + mlir::Twine('_') + newStruct.getSymName()).str()
725 );
726 // Insert 'newStruct' into the parent ModuleOp of the original TemplateOp. Use the
727 // `SymbolTable::insert()` function so that the name will be made unique if necessary.
728 symTables.getSymbolTable(parentModule).insert(newStruct, Block::iterator(parentTemplate));
729 // Drop the old template name from the list.
730 typeAtCallerSymPieces.pop_back();
731 } else { // PARTIAL INSTANTIATION CASE
732 // Clone the template and set instantiated name.
733 TemplateOp newTemplate = parentTemplate.cloneWithoutRegions();
734 newTemplate.setSymName(templateNameWithAttrs);
735 assert(newTemplate->getNumRegions() > 0 && "region exists"); // it just doesn't have a block
736 newTemplate.getBodyRegion().emplaceBlock();
737
738 // Clone preserved const param/expr ops.
739 for (Attribute name : remainingNames) {
740 FlatSymbolRefAttr nameSym = llvm::dyn_cast<FlatSymbolRefAttr>(name);
741 assert(nameSym && "expected FlatSymbolRefAttr");
742
743 Operation *symOp = symTables.getSymbolTable(parentTemplate).lookup(nameSym.getAttr());
744 assert(symOp && "symbol must exist");
745 newTemplate.insert(newTemplate.begin(), symOp->clone());
746 }
747
748 // Insert the struct into the template and the template into the module. Use the
749 // `SymbolTable::insert()` function so that the name will be made unique if necessary.
750 symTables.getSymbolTable(newTemplate).insert(newStruct);
751 symTables.getSymbolTable(parentModule).insert(newTemplate, Block::iterator(parentTemplate));
752
753 // Replace the old template name in the list with the new one (get template name after
754 // symbol table insertion since it may be modified to make it unique).
755 typeAtCallerSymPieces.back() = FlatSymbolRefAttr::get(newTemplate.getSymNameAttr());
756 }
757
758 // Retrieve the new type AFTER inserting since the struct name may be appended to make
759 // it unique and use the remaining non-concrete parameters from the original type.
760 StructType newLocalType = newStruct.getType(reducedCallerParams);
761 typeAtCallerSymPieces.push_back(
762 FlatSymbolRefAttr::get(newLocalType.getNameRef().getLeafReference())
763 );
764 StructType newRemoteType =
765 StructType::get(asSymbolRefAttr(typeAtCallerSymPieces), newLocalType.getParams());
766 LLVM_DEBUG({
767 llvm::dbgs() << "[StructCloner] original def type: " << typeAtDef << '\n';
768 llvm::dbgs() << "[StructCloner] cloned def type: " << newStruct.getType() << '\n';
769 llvm::dbgs() << "[StructCloner] original remote type: " << typeAtCaller << '\n';
770 llvm::dbgs() << "[StructCloner] cloned local type: " << newLocalType << '\n';
771 llvm::dbgs() << "[StructCloner] cloned remote type: " << newRemoteType << '\n';
772 });
773
774 // Within the new struct, replace all references to the original StructType (i.e., the
775 // locally-parameterized version) with the new locally-parameterized StructType,
776 // and replace all uses of the removed struct parameters with the concrete values.
777 MappedTypeConverter tyConv(typeAtDef, newStruct.getType(), paramNameToConcrete);
778 ConversionTarget target =
779 newConverterDefinedTarget<EmitEqualityOp>(tyConv, ctx, tableOffsetIsntSymbol);
780 target.addDynamicallyLegalOp<ConstReadOp>([&paramNameToConcrete](ConstReadOp op) {
781 // Legal if it's not in the map of concrete attribute instantiations
782 return !paramNameToConcrete.contains(op.getConstNameAttr());
783 });
784
785 RewritePatternSet patterns = newGeneralRewritePatternSet<EmitEqualityOp>(tyConv, ctx, target);
786 patterns.add<ClonedBodyConstReadOpPattern>(
787 tyConv, ctx, paramNameToConcrete, tracker_.delayedDiagnosticSet(newLocalType)
788 );
789 patterns.add<ClonedMemberReadOpPattern>(tyConv, ctx, paramNameToConcrete);
790 if (failed(applyFullConversion(newStruct, target, std::move(patterns)))) {
791 LLVM_DEBUG(llvm::dbgs() << "[StructCloner] instantiating body of struct failed \n");
792 return failure();
793 }
794 return newRemoteType;
795 }
796
797public:
798 StructCloner(ConversionTracker &tracker, ModuleOp root)
799 : tracker_(tracker), rootMod(root), symTables() {}
800
801 FailureOr<StructType> createInstantiatedClone(StructType orig) {
802 LLVM_DEBUG(llvm::dbgs() << "[StructCloner] orig: " << orig << '\n');
803 if (ArrayAttr params = orig.getParams()) {
804 return genClone(orig, params.getValue());
805 }
806 LLVM_DEBUG(llvm::dbgs() << "[StructCloner] skip: nullptr for params \n");
807 return failure();
808 }
809
810 void enableReportMissing() { reportMissing = true; }
811
812 void disableReportMissing() { reportMissing = false; }
813};
814
815class DisableReportMissing;
816
817class ParameterizedStructUseTypeConverter : public TypeConverter {
818 ConversionTracker &tracker_;
819 StructCloner cloner;
820
821 friend DisableReportMissing;
822
823public:
824 ParameterizedStructUseTypeConverter(ConversionTracker &tracker, ModuleOp root)
825 : TypeConverter(), tracker_(tracker), cloner(tracker, root) {
826
827 addConversion([](Type inputTy) { return inputTy; });
828
829 addConversion([this](StructType inputTy) -> StructType {
830 LLVM_DEBUG(
831 llvm::dbgs() << "[ParameterizedStructUseTypeConverter] attempting conversion of "
832 << inputTy << '\n';
833 );
834 // First check for a cached entry
835 if (auto opt = tracker_.getInstantiation(inputTy)) {
836 return opt.value();
837 }
838
839 // Otherwise, try to create a clone of the struct with instantiated params. If that can't be
840 // done, return the original type to indicate that it's still legal (for this step at least).
841 FailureOr<StructType> cloneRes = cloner.createInstantiatedClone(inputTy);
842 if (failed(cloneRes)) {
843 return inputTy;
844 }
845 StructType newTy = cloneRes.value();
846 LLVM_DEBUG(
847 llvm::dbgs() << "[ParameterizedStructUseTypeConverter] instantiating " << inputTy
848 << " as " << newTy << '\n'
849 );
850 tracker_.recordInstantiation(inputTy, newTy);
851 return newTy;
852 });
853
854 addConversion([this](ArrayType inputTy) {
855 return inputTy.cloneWith(convertType(inputTy.getElementType()));
856 });
857 }
858};
859
860class CallStructFuncPattern : public OpConversionPattern<CallOp> {
861 ConversionTracker &tracker_;
862
863public:
864 CallStructFuncPattern(TypeConverter &converter, MLIRContext *ctx, ConversionTracker &tracker)
865 // benefit>0 so this applies instead of CallOpClassReplacePattern
866 : OpConversionPattern<CallOp>(converter, ctx, /*benefit=*/1), tracker_(tracker) {}
867
868 LogicalResult matchAndRewrite(
869 CallOp op, OpAdaptor adapter, ConversionPatternRewriter &rewriter
870 ) const override {
871 LLVM_DEBUG(llvm::dbgs() << "[CallStructFuncPattern] CallOp: " << op << '\n');
872
873 // Convert the result types of the CallOp
874 SmallVector<Type> newResultTypes;
875 if (failed(getTypeConverter()->convertTypes(op.getResultTypes(), newResultTypes))) {
876 return op->emitError("Could not convert Op result types.");
877 }
878 LLVM_DEBUG({
879 llvm::dbgs() << "[CallStructFuncPattern] newResultTypes: "
880 << debug::toStringList(newResultTypes) << '\n';
881 });
882
883 // Update the callee to reflect the new struct target if necessary. These checks are based on
884 // `CallOp::calleeIsStructC*()` but the types must not come from the CallOp in this case.
885 // Instead they must come from the converted versions.
886 SymbolRefAttr calleeAttr = op.getCalleeAttr();
887 if (op.calleeIsStructCompute()) {
888 if (StructType newStTy = getIfSingleton<StructType>(newResultTypes)) {
889 LLVM_DEBUG(llvm::dbgs() << "[CallStructFuncPattern] newStTy: " << newStTy << '\n');
890 calleeAttr = appendLeaf(newStTy.getNameRef(), calleeAttr.getLeafReference());
891 tracker_.reportDelayedDiagnostics(newStTy, op);
892 }
893 } else if (op.calleeIsStructConstrain()) {
894 if (StructType newStTy = getAtIndex<StructType>(adapter.getArgOperands().getTypes(), 0)) {
895 LLVM_DEBUG(llvm::dbgs() << "[CallStructFuncPattern] newStTy: " << newStTy << '\n');
896 calleeAttr = appendLeaf(newStTy.getNameRef(), calleeAttr.getLeafReference());
897 }
898 }
899
900 LLVM_DEBUG(llvm::dbgs() << "[CallStructFuncPattern] replaced " << op);
902 rewriter, op, newResultTypes, calleeAttr, adapter.getMapOperands(),
903 op.getNumDimsPerMapAttr(), adapter.getArgOperands()
904 );
905 (void)newOp; // tell compiler it's intentionally unused in release builds
906 LLVM_DEBUG(llvm::dbgs() << " with " << newOp << '\n');
907 return success();
908 }
909};
910
911// This one ensures MemberDefOp types are converted even if there are no reads/writes to them.
912class MemberDefOpPattern : public OpConversionPattern<MemberDefOp> {
913public:
914 MemberDefOpPattern(TypeConverter &converter, MLIRContext *ctx, ConversionTracker &)
915 // benefit>0 so this applies instead of GeneralTypeReplacePattern<MemberDefOp>
916 : OpConversionPattern<MemberDefOp>(converter, ctx, /*benefit=*/1) {}
917
918 LogicalResult matchAndRewrite(
919 MemberDefOp op, OpAdaptor /*adapter*/, ConversionPatternRewriter &rewriter
920 ) const override {
921 LLVM_DEBUG(llvm::dbgs() << "[MemberDefOpPattern] MemberDefOp: " << op << '\n');
922
923 Type oldMemberType = op.getType();
924 Type newMemberType = getTypeConverter()->convertType(oldMemberType);
925 if (oldMemberType == newMemberType) {
926 return failure(); // nothing changed
927 }
928 rewriter.modifyOpInPlace(op, [&op, &newMemberType]() { op.setType(newMemberType); });
929 return success();
930 }
931};
932
935class DisableReportMissing : public LegalityCheckCallback {
936 ParameterizedStructUseTypeConverter &tyConv;
937
938public:
939 explicit DisableReportMissing(ParameterizedStructUseTypeConverter &tc) : tyConv(tc) {}
940
941 void checkStarted() override { tyConv.cloner.disableReportMissing(); }
942
943 void checkEnded(bool) override { tyConv.cloner.enableReportMissing(); }
944};
945
946LogicalResult run(ModuleOp modOp, ConversionTracker &tracker) {
947 MLIRContext *ctx = modOp.getContext();
948 ParameterizedStructUseTypeConverter tyConv(tracker, modOp);
949 DisableReportMissing drm(tyConv);
950 ConversionTarget target = newConverterDefinedTargetWithCallback<>(tyConv, ctx, drm);
951 RewritePatternSet patterns = newGeneralRewritePatternSet(tyConv, ctx, target);
952 patterns.add<CallStructFuncPattern, MemberDefOpPattern>(tyConv, ctx, tracker);
953 return applyPartialConversion(modOp, target, std::move(patterns));
954}
955
958LogicalResult instantiateMainStruct(ModuleOp modOp, ConversionTracker &tracker) {
959 FailureOr<StructType> mainTypeOpt = getMainInstanceType(modOp);
960 if (failed(mainTypeOpt)) {
961 return failure();
962 }
963
964 StructType mainType = mainTypeOpt.value();
965 if (!mainType || isNullOrEmpty(mainType.getParams()) || tracker.getInstantiation(mainType)) {
966 return success();
967 }
968
969 StructCloner cloner(tracker, modOp);
970 FailureOr<StructType> cloneRes = cloner.createInstantiatedClone(mainType);
971 if (failed(cloneRes)) {
972 return failure();
973 }
974
975 StructType instantiatedMainType = cloneRes.value();
976 tracker.recordInstantiation(mainType, instantiatedMainType);
977 modOp->setAttr(MAIN_ATTR_NAME, TypeAttr::get(instantiatedMainType));
978 return success();
979}
980
981} // namespace Step1_InstantiateStructs
982
984
987class FuncInstTypeConverter : public TypeConverter {
988 DenseMap<Attribute, Attribute> paramNameToValue;
989
990 Attribute convertIfPossible(Attribute a) const {
991 auto res = paramNameToValue.find(a);
992 return (res != paramNameToValue.end()) ? res->second : a;
993 }
994
995public:
996 explicit FuncInstTypeConverter(DenseMap<Attribute, Attribute> paramNameToConcrete)
997 : TypeConverter(), paramNameToValue(std::move(paramNameToConcrete)) {
998 addConversion([](Type t) { return t; });
999
1000 addConversion([this](TypeVarType inputTy) -> Type {
1001 if (TypeAttr tyAttr = llvm::dyn_cast<TypeAttr>(convertIfPossible(inputTy.getNameRef()))) {
1002 Type convertedType = tyAttr.getValue();
1003 if (isConcreteType(convertedType)) {
1004 return convertedType;
1005 }
1006 }
1007 return inputTy;
1008 });
1009
1010 addConversion([this](ArrayType inputTy) {
1011 SmallVector<Attribute> updated;
1012 bool changed = false;
1013 for (Attribute a : inputTy.getDimensionSizes()) {
1014 Attribute converted = convertIfPossible(a);
1015 updated.push_back(converted);
1016 if (converted != a) {
1017 changed = true;
1018 }
1019 }
1020 Type newElemTy = this->convertType(inputTy.getElementType());
1021 if (!changed && newElemTy == inputTy.getElementType()) {
1022 return inputTy;
1023 }
1025 inputTy.cloneWith(inputTy.getElementType(), updated), newElemTy
1026 );
1027 });
1028
1029 addConversion([this](StructType inputTy) -> StructType {
1030 if (ArrayAttr params = inputTy.getParams()) {
1031 SmallVector<Attribute> updated;
1032 bool changed = false;
1033 for (Attribute a : params) {
1034 if (TypeAttr ta = dyn_cast<TypeAttr>(a)) {
1035 Type newTy = this->convertType(ta.getValue());
1036 if (newTy != ta.getValue()) {
1037 updated.push_back(TypeAttr::get(newTy));
1038 changed = true;
1039 continue;
1040 }
1041 } else {
1042 Attribute converted = convertIfPossible(a);
1043 if (converted != a) {
1044 updated.push_back(converted);
1045 changed = true;
1046 continue;
1047 }
1048 }
1049 updated.push_back(a);
1050 }
1051 if (changed) {
1052 return getStructTypeWithParams(inputTy.getNameRef(), inputTy.getContext(), updated);
1053 }
1054 }
1055 return inputTy;
1056 });
1057 }
1058
1059 Attribute convertAttr(Attribute attr) const {
1060 if (TypeAttr tyAttr = llvm::dyn_cast<TypeAttr>(attr)) {
1061 Type convertedTy = convertType(tyAttr.getValue());
1062 if (convertedTy != tyAttr.getValue()) {
1063 return TypeAttr::get(convertedTy);
1064 }
1065 }
1066 return convertIfPossible(attr);
1067 }
1068
1069 bool containsParam(Attribute nameAttr) const { return paramNameToValue.contains(nameAttr); }
1070 const DenseMap<Attribute, Attribute> &getParamMap() const { return paramNameToValue; }
1071};
1072
1074inline static std::optional<Attribute>
1075inferUnifiedParam(const UnificationMap &unifyResult, SymbolRefAttr paramName) {
1076 auto it = unifyResult.find({paramName, Side::RHS});
1077 return (it == unifyResult.end()) ? std::nullopt : std::make_optional(it->second);
1078}
1079
1082inline static LogicalResult failIncompatibleInferredParam(
1083 CallOp op, PatternRewriter &rewriter, FlatSymbolRefAttr paramName, TemplateParamOp paramOp
1084) {
1085 LLVM_DEBUG(
1086 llvm::dbgs() << "[InstantiateFuncAtCallOp] unification for param '" << paramName
1087 << "': incompatible with specified param type. MUST FAIL!\n"
1088 );
1089 return rewriter.notifyMatchFailure(op, [&paramName, &paramOp](Diagnostic &diag) {
1090 diag.append("inferred value for parameter '")
1091 .append(paramName)
1092 .append("' is incompatible with specified param type")
1093 .attachNote(paramOp.getLoc())
1094 .append("template parameter declared here");
1095 });
1096}
1097
1100class WildcardTypeBodyInferer final {
1101 SymbolTableCollection &symTables_;
1102 const DenseMap<Attribute, Attribute> &paramNameToConcrete_;
1103 SmallVector<std::pair<Operation *, FlatSymbolRefAttr>> activeInferences_;
1104
1105public:
1106 WildcardTypeBodyInferer(
1107 SymbolTableCollection &symTables, const DenseMap<Attribute, Attribute> &paramNameToConcrete
1108 )
1109 : symTables_(symTables), paramNameToConcrete_(paramNameToConcrete) {}
1110
1111 std::optional<Attribute> infer(FuncDefOp func, FlatSymbolRefAttr paramName) {
1112 if (llvm::any_of(activeInferences_, [&](const auto &e) {
1113 return e.first == func.getOperation() && e.second == paramName;
1114 })) {
1115 return std::nullopt;
1116 }
1117 activeInferences_.emplace_back(func.getOperation(), paramName);
1118
1119 FuncInstTypeConverter tyConv((paramNameToConcrete_));
1120 std::optional<Attribute> inferred;
1121 bool ambiguous = false;
1122
1123 // Record a concrete candidate unless it conflicts with an earlier one, in which
1124 // case the wildcard is treated as ambiguous and left unresolved.
1125 auto noteCandidate = [&inferred, &ambiguous](Attribute candidate) {
1126 if (!candidate || !isConcreteAttr(candidate)) {
1127 return WalkResult::advance();
1128 }
1129 if (!inferred.has_value()) {
1130 inferred = candidate;
1131 return WalkResult::advance();
1132 }
1133 if (*inferred != candidate) {
1134 ambiguous = true;
1135 return WalkResult::interrupt();
1136 }
1137 return WalkResult::advance();
1138 };
1139
1140 WalkResult walkResult = func.walk([&](Operation *bodyOp) {
1141 if (auto castOp = llvm::dyn_cast<UnifiableCastOp>(bodyOp)) {
1142 Type inputTy = tyConv.convertType(castOp.getInput().getType());
1143 Type resultTy = tyConv.convertType(castOp.getResult().getType());
1144 if (auto inputTvar = llvm::dyn_cast<TypeVarType>(inputTy);
1145 inputTvar && inputTvar.getNameRef() == paramName && isConcreteType(resultTy)) {
1146 return noteCandidate(TypeAttr::get(resultTy));
1147 }
1148 if (auto resultTvar = llvm::dyn_cast<TypeVarType>(resultTy);
1149 resultTvar && resultTvar.getNameRef() == paramName && isConcreteType(inputTy)) {
1150 return noteCandidate(TypeAttr::get(inputTy));
1151 }
1152 return WalkResult::advance();
1153 }
1154
1155 auto nestedCall = llvm::dyn_cast<CallOp>(bodyOp);
1156 if (!nestedCall) {
1157 return WalkResult::advance();
1158 }
1159
1160 FailureOr<SymbolLookupResult<FuncDefOp>> nestedTgtOpt =
1161 nestedCall.getCalleeTarget(symTables_);
1162 if (failed(nestedTgtOpt)) {
1163 return WalkResult::advance();
1164 }
1165 FuncDefOp nestedTgt = nestedTgtOpt->get();
1166 auto nestedTemplate = llvm::dyn_cast<TemplateOp>(nestedTgt->getParentOp());
1167 if (!nestedTemplate) {
1168 return WalkResult::advance();
1169 }
1170
1171 TypeRange nestedResultTypes = nestedTgt.getFunctionType().getResults();
1172 for (auto [result, nestedResultTy] :
1173 llvm::zip_equal(nestedCall.getResults(), nestedResultTypes)) {
1174 Type convertedResultTy = tyConv.convertType(result.getType());
1175 auto resultTvar = llvm::dyn_cast<TypeVarType>(convertedResultTy);
1176 auto nestedTvar = llvm::dyn_cast<TypeVarType>(nestedResultTy);
1177 if (!resultTvar || !nestedTvar || resultTvar.getNameRef() != paramName) {
1178 continue;
1179 }
1180 if (std::optional<Attribute> candidate = inferFromExplicitNestedCallParams(
1181 nestedCall, nestedTemplate, nestedTvar.getNameRef(), tyConv
1182 )) {
1183 WalkResult candidateResult = noteCandidate(*candidate);
1184 if (candidateResult.wasInterrupted()) {
1185 return candidateResult;
1186 }
1187 continue;
1188 }
1189 if (std::optional<Attribute> candidate = infer(nestedTgt, nestedTvar.getNameRef())) {
1190 WalkResult candidateResult = noteCandidate(*candidate);
1191 if (candidateResult.wasInterrupted()) {
1192 return candidateResult;
1193 }
1194 }
1195 }
1196 return WalkResult::advance();
1197 });
1198
1199 activeInferences_.pop_back();
1200 if (ambiguous || (walkResult.wasInterrupted() && !inferred.has_value())) {
1201 return std::nullopt;
1202 }
1203 return inferred;
1204 }
1205
1206private:
1207 std::optional<Attribute> inferFromExplicitNestedCallParams(
1208 CallOp nestedCall, TemplateOp nestedTemplate, FlatSymbolRefAttr nestedParamName,
1209 const FuncInstTypeConverter &tyConv
1210 ) const {
1211 ArrayAttr nestedCallParams = nestedCall.getTemplateParamsAttr();
1212 if (isNullOrEmpty(nestedCallParams)) {
1213 return std::nullopt;
1214 }
1215
1216 for (auto [paramOp, attr] :
1217 llvm::zip_equal(nestedTemplate.getConstOps<TemplateParamOp>(), nestedCallParams)) {
1218 auto paramName = FlatSymbolRefAttr::get(paramOp.getSymNameAttr());
1219 if (paramName != nestedParamName) {
1220 continue;
1221 }
1222 Attribute convertedAttr = tyConv.convertAttr(attr);
1223 return isConcreteAttr(convertedAttr) ? std::make_optional(convertedAttr) : std::nullopt;
1224 }
1225 return std::nullopt;
1226 }
1227};
1228
1231class ClonedBodyArrayReadOpPattern final : public OpConversionPattern<ReadArrayOp> {
1232public:
1233 using OpConversionPattern<ReadArrayOp>::OpConversionPattern;
1234
1235 LogicalResult matchAndRewrite(
1236 ReadArrayOp op, OpAdaptor adaptor, ConversionPatternRewriter &rewriter
1237 ) const override {
1238 Type newResultTy = getTypeConverter()->convertType(op.getResult().getType());
1239 if (!llvm::isa<ArrayType>(newResultTy)) {
1240 return failure();
1241 }
1243 rewriter, op, newResultTy, adaptor.getArrRef(), adaptor.getIndices()
1244 );
1245 return success();
1246 }
1247};
1248
1251class ClonedBodyArrayWriteOpPattern final : public OpConversionPattern<WriteArrayOp> {
1252public:
1253 using OpConversionPattern<WriteArrayOp>::OpConversionPattern;
1254
1255 LogicalResult matchAndRewrite(
1256 WriteArrayOp op, OpAdaptor adaptor, ConversionPatternRewriter &rewriter
1257 ) const override {
1258 if (!llvm::isa<ArrayType>(adaptor.getRvalue().getType())) {
1259 return failure();
1260 }
1262 rewriter, op, adaptor.getArrRef(), adaptor.getIndices(), adaptor.getRvalue()
1263 );
1264 return success();
1265 }
1266};
1267
1271static LogicalResult applyBodyConversions(
1272 CallOp op, FuncDefOp newFunc, const DenseMap<Attribute, Attribute> &paramNameToConcrete
1273) {
1274 MLIRContext *ctx = op.getContext();
1275 FuncInstTypeConverter tyConv(paramNameToConcrete);
1276 ConversionTarget target = newConverterDefinedTarget<>(tyConv, ctx, tableOffsetIsntSymbol);
1277 target.addDynamicallyLegalOp<ConstReadOp>([&tyConv](ConstReadOp p) {
1278 // Legal if it's not in the map of concrete attribute instantiations
1279 return !tyConv.containsParam(p.getConstNameAttr());
1280 });
1281 SmallVector<Diagnostic> delayedDiagnostics;
1282 RewritePatternSet bodyPatterns = newGeneralRewritePatternSet(tyConv, ctx, target);
1283 bodyPatterns.add<ClonedBodyConstReadOpPattern>(
1284 tyConv, ctx, tyConv.getParamMap(), delayedDiagnostics
1285 );
1286 bodyPatterns.add<ClonedBodyArrayReadOpPattern, ClonedBodyArrayWriteOpPattern>(tyConv, ctx);
1287 bodyPatterns.add<ClonedMemberReadOpPattern>(tyConv, ctx, paramNameToConcrete);
1288 if (failed(applyFullConversion(newFunc, target, std::move(bodyPatterns)))) {
1289 return failure();
1290 }
1291 LLVM_DEBUG(llvm::dbgs() << "[InstantiateFuncAtCallOp] instantiated clone: " << newFunc << '\n');
1292 ::reportDelayedDiagnostics(op, std::move(delayedDiagnostics));
1293
1294 SymbolTableCollection tables;
1295 WalkResult res = newFunc.walk([&tables](CallOp nestedCall) {
1296 return WalkResult(nestedCall.verifySymbolUses(tables));
1297 });
1298 return failure(res.wasInterrupted());
1299}
1300
1301class InstantiateFuncAtCallOp final : public OpRewritePattern<CallOp> {
1302 ConversionTracker &tracker_;
1303
1304public:
1305 InstantiateFuncAtCallOp(MLIRContext *ctx, ConversionTracker &tracker)
1306 : OpRewritePattern<CallOp>(ctx), tracker_(tracker) {}
1307
1308 LogicalResult matchAndRewrite(CallOp op, PatternRewriter &rewriter) const override {
1309 LLVM_DEBUG(llvm::dbgs() << "[InstantiateFuncAtCallOp] op: " << op << '\n');
1310
1311 if (calleeReferencesTemplateParam(op)) {
1312 return failure();
1313 }
1314
1315 // Lookup callee target function
1316 SymbolTableCollection symTables;
1317 FailureOr<SymbolLookupResult<FuncDefOp>> callTgtOpt = op.getCalleeTarget(symTables);
1318 if (failed(callTgtOpt)) {
1319 return rewriter.notifyMatchFailure(op, [](Diagnostic &diag) {
1320 diag << "could not find target function for call";
1321 });
1322 }
1323 FuncDefOp callTgt = callTgtOpt->get();
1324
1325 // Check if callee is within a TemplateOp
1326 TemplateOp parentTemplate = llvm::dyn_cast<TemplateOp>(callTgt->getParentOp());
1327 if (!parentTemplate) {
1328 return failure(); // nothing to do if not parameterized
1329 }
1330 LLVM_DEBUG(
1331 llvm::dbgs() << "[InstantiateFuncAtCallOp] target function in template "
1332 << parentTemplate.getSymName() << '\n'
1333 );
1334
1335 // Perform type unification with tracking to infer the instantiated type(s). Even though
1336 // `CallOp` verification already checked that caller and callee types unify, the progress of
1337 // instantiation so far may have brought together a chain of calls across templates where each
1338 // individual unification check passed due to permissive type variables and/or symbols in the
1339 // middle but the overall chain does not unify. Hence, this unification may fail and should
1340 // produce a meaningful error message if it does.
1341 // See: `test/Transforms/Flattening/instantiate_funcs_fail.llzk`
1342 FailureOr<UnificationMap> unifyResult = unifyTypeSignature(op, callTgt, rewriter);
1343 if (failed(unifyResult)) {
1344 return failure();
1345 }
1346 LLVM_DEBUG(
1347 llvm::dbgs() << "[InstantiateFuncAtCallOp] unifications of types: "
1348 << debug::toStringList(unifyResult.value()) << '\n'
1349 );
1350
1351 // Maps template parameter symbols to the instantiation value at the call site.
1352 DenseMap<Attribute, Attribute> paramNameToConcrete;
1353 if (failed(collectConcreteTemplateParams(
1354 op, rewriter, symTables, callTgt, parentTemplate, unifyResult.value(),
1355 paramNameToConcrete
1356 ))) {
1357 return failure();
1358 }
1359
1360 if (paramNameToConcrete.empty()) {
1361 LLVM_DEBUG(llvm::dbgs() << "[InstantiateFuncAtCallOp] skip: no concrete params\n");
1362 return failure();
1363 }
1364
1365 evaluateTemplateExprs(parentTemplate, paramNameToConcrete);
1366
1367 InstantiationLayout layout =
1368 buildInstantiationLayout(parentTemplate, op.getTemplateParamsAttr(), paramNameToConcrete);
1369 ModuleOp parentModule = getParentOfType<ModuleOp>(parentTemplate);
1370 assert(parentModule && "TemplateOp must be nested in a ModuleOp");
1371
1372 SymbolRefAttr originalCalleeAttr = op.getCalleeAttr();
1373 FailureOr<SymbolRefAttr> newCalleeAttr =
1374 layout.remainingNames.empty()
1375 ? instantiateFully(
1376 op, rewriter, symTables, callTgt, parentTemplate, parentModule,
1377 layout.templateNameWithAttrs, paramNameToConcrete
1378 )
1379 : instantiatePartially(
1380 op, rewriter, symTables, callTgt, parentTemplate, parentModule, layout,
1381 paramNameToConcrete
1382 );
1383 if (failed(newCalleeAttr)) {
1384 return failure();
1385 }
1386
1387 tracker_.recordInstantiation(originalCalleeAttr);
1388
1389 // Update the CallOp to point to the instantiated function and mark the module as modified.
1390 rewriter.modifyOpInPlace(op, [&op, &newCalleeAttr, &layout]() {
1391 LLVM_DEBUG({
1392 llvm::dbgs() << "[InstantiateFuncAtCallOp] updating callee from " << op.getCalleeAttr()
1393 << " to " << *newCalleeAttr << '\n';
1394 });
1395 op.setCalleeAttr(*newCalleeAttr);
1397 });
1398 tracker_.updateModifiedFlag(true);
1399 return success();
1400 }
1401
1402private:
1405 static FailureOr<UnificationMap>
1406 unifyTypeSignature(CallOp op, FuncDefOp callTgt, PatternRewriter &rewriter) {
1407 FailureOr<UnificationMap> unifyResult = op.unifyTypeSignature(callTgt.getFunctionType());
1408 if (succeeded(unifyResult)) {
1409 return unifyResult;
1410 }
1411 return rewriter.notifyMatchFailure(op, [&](Diagnostic &diag) {
1412 diag.append("target function type does not unify with call type ")
1413 .append(op.getTypeSignature())
1414 .attachNote(callTgt.getLoc())
1415 .append("target function declared here");
1416 });
1417 }
1418
1421 static LogicalResult collectConcreteTemplateParams(
1422 CallOp op, PatternRewriter &rewriter, SymbolTableCollection &symTables, FuncDefOp callTgt,
1423 TemplateOp parentTemplate, const UnificationMap &unifyResult,
1424 DenseMap<Attribute, Attribute> &paramNameToConcrete
1425 ) {
1426 auto realParams = parentTemplate.getConstOps<TemplateParamOp>();
1427 ArrayAttr callParams = op.getTemplateParamsAttr();
1428 LLVM_DEBUG(
1429 llvm::dbgs() << "[InstantiateFuncAtCallOp] TemplateParamsAttr: " << callParams << '\n'
1430 );
1431
1432 auto recordConcreteParam = [&](FlatSymbolRefAttr paramName, TemplateParamOp paramOp,
1433 Attribute concreteValue) {
1434 if (failed(op.verifyTemplateParamCompatibility(concreteValue, paramOp))) {
1435 return failIncompatibleInferredParam(op, rewriter, paramName, paramOp);
1436 }
1437 paramNameToConcrete[paramName] = concreteValue;
1438 return success();
1439 };
1440
1441 // If there's no template instantiation list, must infer all template parameters.
1442 if (isNullOrEmpty(callParams)) {
1443 for (auto paramOp : realParams) {
1444 auto paramName = FlatSymbolRefAttr::get(paramOp.getSymNameAttr());
1445 auto inferredValOpt = inferUnifiedParam(unifyResult, paramName);
1446 if (!inferredValOpt.has_value()) {
1447 LLVM_DEBUG(
1448 llvm::dbgs() << "[InstantiateFuncAtCallOp] unification for param '" << paramName
1449 << "': not found\n"
1450 );
1451 continue;
1452 }
1453 Attribute inferredVal = *inferredValOpt;
1454 LLVM_DEBUG(
1455 llvm::dbgs() << "[InstantiateFuncAtCallOp] inferredVal: " << inferredVal << '\n'
1456 );
1457 if (!isConcreteAttr(inferredVal)) {
1458 LLVM_DEBUG(
1459 llvm::dbgs() << "[InstantiateFuncAtCallOp] unification for param '" << paramName
1460 << "': not concrete, " << inferredVal << '\n'
1461 );
1462 continue;
1463 }
1464 if (failed(recordConcreteParam(paramName, paramOp, inferredVal))) {
1465 return failure();
1466 }
1467 }
1468 return success();
1469 }
1470
1471 // As stated earlier, need to run the verification checks again to ensure the
1472 // instantiation is valid, except for the size check because that cannot change.
1473 assert((callParams.size() == llvm::range_size(realParams)) && "per CallOpVerifier");
1474 if (failed(op.verifyTemplateParamCompatibility(realParams))) {
1475 return rewriter.notifyMatchFailure(op, [&](Diagnostic &diag) {
1476 diag.append("incompatible with specified param type(s)");
1477 });
1478 }
1479 if (failed(op.verifyTemplateParamsMatchInferred(realParams, unifyResult))) {
1480 return rewriter.notifyMatchFailure(op, [&](Diagnostic &diag) {
1481 diag.append("incompatible with inferred param value(s)");
1482 });
1483 }
1484
1485 // When template parameters are specified on the CallOp, use them as the source of truth
1486 // for concrete arguments, then infer wildcard parameters against the full explicit map.
1487 SmallVector<std::pair<TemplateParamOp, FlatSymbolRefAttr>> wildcardParams;
1488 for (auto [paramOp, attr] : llvm::zip_equal(realParams, callParams.getValue())) {
1489 auto paramName = FlatSymbolRefAttr::get(paramOp.getSymNameAttr());
1490 AttrConcreteness classification = classifyAttrConcreteness(attr);
1491 if (classification == AttrConcreteness::Concrete) {
1492 paramNameToConcrete[paramName] = attr;
1493 continue;
1494 }
1495
1496 if (classification == AttrConcreteness::NonConcrete) {
1497 LLVM_DEBUG(
1498 llvm::dbgs() << "[InstantiateFuncAtCallOp] unification for param '" << paramName
1499 << "': not concrete, " << attr << '\n'
1500 );
1501 continue;
1502 }
1503 wildcardParams.emplace_back(paramOp, paramName);
1504 }
1505
1506 WildcardTypeBodyInferer bodyInferer(symTables, paramNameToConcrete);
1507 for (auto [paramOp, paramName] : wildcardParams) {
1508 auto inferredValOpt = inferUnifiedParam(unifyResult, paramName);
1509 if (inferredValOpt.has_value() && isConcreteAttr(*inferredValOpt)) {
1510 LLVM_DEBUG(
1511 llvm::dbgs() << "[InstantiateFuncAtCallOp] inferredVal: " << *inferredValOpt << '\n'
1512 );
1513 if (failed(recordConcreteParam(paramName, paramOp, *inferredValOpt))) {
1514 return failure();
1515 }
1516 continue;
1517 }
1518
1519 inferredValOpt = bodyInferer.infer(callTgt, paramName);
1520 if (inferredValOpt.has_value() && isConcreteAttr(*inferredValOpt)) {
1521 LLVM_DEBUG(
1522 llvm::dbgs() << "[InstantiateFuncAtCallOp] body-inferred value for param '"
1523 << paramName << "': " << *inferredValOpt << '\n'
1524 );
1525 if (failed(recordConcreteParam(paramName, paramOp, *inferredValOpt))) {
1526 return failure();
1527 }
1528 }
1529 }
1530 return success();
1531 }
1532
1535 static FailureOr<SymbolRefAttr> instantiateFully(
1536 CallOp op, PatternRewriter &rewriter, SymbolTableCollection &symTables, FuncDefOp callTgt,
1537 TemplateOp parentTemplate, ModuleOp parentModule, StringRef templateNameWithAttrs,
1538 const DenseMap<Attribute, Attribute> &paramNameToConcrete
1539 ) {
1540 MLIRContext *ctx = op.getContext();
1541 std::string newFuncName =
1542 (mlir::Twine(templateNameWithAttrs) + "_" + callTgt.getSymName()).str();
1543 StringRef actualNewFuncName = newFuncName;
1544 if (!symTables.getSymbolTable(parentModule).lookup(newFuncName)) {
1545 FuncDefOp newFunc = callTgt.clone();
1546 newFunc.setSymName(newFuncName);
1547 convertCalleesInPlace(newFunc, paramNameToConcrete);
1548 // Insert before the TemplateOp; symbol table may adjust the name to ensure uniqueness.
1549 symTables.getSymbolTable(parentModule).insert(newFunc, Block::iterator(parentTemplate));
1550 actualNewFuncName = newFunc.getSymName();
1551 LLVM_DEBUG(
1552 llvm::dbgs() << "[InstantiateFuncAtCallOp] created full instantiation function: "
1553 << actualNewFuncName << '\n'
1554 );
1555 if (failed(applyBodyConversions(op, newFunc, paramNameToConcrete))) {
1556 LLVM_DEBUG(
1557 llvm::dbgs() << "[InstantiateFuncAtCallOp] body conversion failed for "
1558 << actualNewFuncName << '\n'
1559 );
1560 newFunc->erase();
1561 return rewriter.notifyMatchFailure(op, [&](Diagnostic &diag) {
1562 diag.append("failure while creating instantiated function '", actualNewFuncName, '\'');
1563 });
1564 }
1565 } else {
1566 LLVM_DEBUG(
1567 llvm::dbgs() << "[InstantiateFuncAtCallOp] reusing full instantiation function: "
1568 << actualNewFuncName << '\n'
1569 );
1570 }
1571
1572 // Callee: drop template & original function names, add the new module-level function name.
1573 // Original: @[prefix...]::@TemplateName::@funcName
1574 // New: @[prefix...]::@newFuncName
1575 SmallVector<FlatSymbolRefAttr> symPieces = getPieces(op.getCalleeAttr());
1576 assert(symPieces.size() >= 2 && "callee must include at least template and function names");
1577 symPieces.pop_back(); // remove original function name
1578 symPieces.pop_back(); // remove template name
1579 symPieces.push_back(FlatSymbolRefAttr::get(StringAttr::get(ctx, actualNewFuncName)));
1580 return asSymbolRefAttr(symPieces);
1581 }
1582
1587 static FailureOr<SymbolRefAttr> instantiatePartially(
1588 CallOp op, PatternRewriter &rewriter, SymbolTableCollection &symTables, FuncDefOp callTgt,
1589 TemplateOp parentTemplate, ModuleOp parentModule, const InstantiationLayout &layout,
1590 const DenseMap<Attribute, Attribute> &paramNameToConcrete
1591 ) {
1592 TemplateOp newTemplate;
1593 if (Operation *existing =
1594 symTables.getSymbolTable(parentModule).lookup(layout.templateNameWithAttrs)) {
1595 newTemplate = llvm::dyn_cast<TemplateOp>(existing);
1596 }
1597 if (!newTemplate) {
1598 newTemplate = parentTemplate.cloneWithoutRegions();
1599 newTemplate.setSymName(layout.templateNameWithAttrs);
1600 assert(newTemplate->getNumRegions() > 0 && "region exists");
1601 newTemplate.getBodyRegion().emplaceBlock();
1602
1603 Block &newTemplateBody = newTemplate.getBodyRegion().front();
1604 for (Attribute name : layout.remainingNames) {
1605 FlatSymbolRefAttr nameSym = llvm::cast<FlatSymbolRefAttr>(name);
1606 Operation *paramOp = symTables.getSymbolTable(parentTemplate).lookup(nameSym.getAttr());
1607 assert(paramOp && "symbol must exist");
1608 newTemplateBody.push_back(paramOp->clone());
1609 }
1610
1611 // Clone and partially convert the function (concretize only the concrete params).
1612 FuncDefOp newFunc = callTgt.clone();
1613 convertCalleesInPlace(newFunc, paramNameToConcrete);
1614
1615 // Insert before body conversion so nested concrete callees verify from the root module. Use
1616 // the `SymbolTable::insert()` function so that the name will be made unique if necessary.
1617 symTables.getSymbolTable(newTemplate).insert(newFunc);
1618 symTables.getSymbolTable(parentModule).insert(newTemplate, Block::iterator(parentTemplate));
1619 if (failed(applyBodyConversions(op, newFunc, paramNameToConcrete))) {
1620 StringRef newFuncName = newFunc.getSymName();
1621 LLVM_DEBUG(
1622 llvm::dbgs() << "[InstantiateFuncAtCallOp] body conversion failed for " << newFuncName
1623 << '\n'
1624 );
1625 newTemplate->erase();
1626 return rewriter.notifyMatchFailure(op, [&](Diagnostic &diag) {
1627 diag.append("failure while creating instantiated function '", newFuncName, '\'');
1628 });
1629 }
1630
1631 LLVM_DEBUG(
1632 llvm::dbgs() << "[InstantiateFuncAtCallOp] created partial instantiation template: "
1633 << newTemplate.getSymName() << '\n'
1634 );
1635 } else {
1636 LLVM_DEBUG(
1637 llvm::dbgs() << "[InstantiateFuncAtCallOp] reusing partial instantiation template: "
1638 << newTemplate.getSymName() << '\n'
1639 );
1640 }
1641
1642 // Callee: replace old template name with new template name, keep the function name.
1643 // Original: @[prefix...]::@TemplateName::@funcName
1644 // New: @[prefix...]::@newTemplateName::@funcName
1645 SmallVector<FlatSymbolRefAttr> symPieces = getPieces(op.getCalleeAttr());
1646 assert(symPieces.size() >= 2 && "callee must include at least template and function names");
1647 symPieces.pop_back(); // remove original function name (will be re-appended)
1648 symPieces.pop_back(); // remove original template name
1649 symPieces.push_back(FlatSymbolRefAttr::get(newTemplate.getSymNameAttr()));
1650 symPieces.push_back(FlatSymbolRefAttr::get(callTgt.getSymNameAttr()));
1651 return asSymbolRefAttr(symPieces);
1652 }
1653};
1654
1655LogicalResult run(ModuleOp modOp, ConversionTracker &tracker) {
1656 MLIRContext *ctx = modOp.getContext();
1657 RewritePatternSet patterns(ctx);
1658 patterns.add<InstantiateFuncAtCallOp>(ctx, tracker);
1659 MatchFailureListener failureListener;
1660 walkAndApplyPatterns(modOp, std::move(patterns), &failureListener);
1661 return failure(failureListener.hadFailure);
1662}
1663
1664} // namespace Step2_InstantiateFunctions
1665
1666namespace Step3_Unroll {
1667
1668// TODO: not guaranteed to work with WhileOp, can try with our custom attributes though.
1669template <HasInterface<LoopLikeOpInterface> OpClass>
1670class LoopUnrollPattern : public OpRewritePattern<OpClass> {
1671public:
1672 using OpRewritePattern<OpClass>::OpRewritePattern;
1673
1674 LogicalResult matchAndRewrite(OpClass loopOp, PatternRewriter &rewriter) const override {
1675 if (auto maybeConstant = getConstantTripCount(loopOp)) {
1676 uint64_t tripCount = *maybeConstant;
1677 if (tripCount == 0) {
1678 rewriter.eraseOp(loopOp);
1679 return success();
1680 } else if (tripCount == 1) {
1681 return loopOp.promoteIfSingleIteration(rewriter);
1682 }
1683 return loopUnrollByFactor(loopOp, tripCount);
1684 }
1685 return failure();
1686 }
1687
1688private:
1691 static std::optional<int64_t> getConstantTripCount(LoopLikeOpInterface loopOp) {
1692 std::optional<OpFoldResult> lbVal = loopOp.getSingleLowerBound();
1693 std::optional<OpFoldResult> ubVal = loopOp.getSingleUpperBound();
1694 std::optional<OpFoldResult> stepVal = loopOp.getSingleStep();
1695 if (!lbVal.has_value() || !ubVal.has_value() || !stepVal.has_value()) {
1696 return std::nullopt;
1697 }
1698 return constantTripCount(lbVal.value(), ubVal.value(), stepVal.value());
1699 }
1700};
1701
1702LogicalResult run(ModuleOp modOp, ConversionTracker &tracker) {
1703 MLIRContext *ctx = modOp.getContext();
1704 RewritePatternSet patterns(ctx);
1705 patterns.add<LoopUnrollPattern<scf::ForOp>>(ctx);
1706 patterns.add<LoopUnrollPattern<affine::AffineForOp>>(ctx);
1707
1708 return applyAndFoldGreedily(modOp, tracker, std::move(patterns));
1709}
1710} // namespace Step3_Unroll
1711
1713
1714// Adapted from `mlir::getConstantIntValues()` but that one failed in CI for an unknown reason. This
1715// version uses a basic loop instead of llvm::map_to_vector().
1716std::optional<SmallVector<int64_t>> getConstantIntValues(ArrayRef<OpFoldResult> ofrs) {
1717 SmallVector<int64_t> res;
1718 for (OpFoldResult ofr : ofrs) {
1719 std::optional<int64_t> cv = getConstantIntValue(ofr);
1720 if (!cv.has_value()) {
1721 return std::nullopt;
1722 }
1723 res.push_back(cv.value());
1724 }
1725 return res;
1726}
1727
1728struct AffineMapFolder {
1729 struct Input {
1730 OperandRangeRange mapOpGroups;
1731 DenseI32ArrayAttr dimsPerGroup;
1732 ArrayRef<Attribute> paramsOfStructTy;
1733 };
1734
1735 struct Output {
1736 SmallVector<SmallVector<Value>> mapOpGroups;
1737 SmallVector<int32_t> dimsPerGroup;
1738 SmallVector<Attribute> paramsOfStructTy;
1739 };
1740
1741 static inline SmallVector<ValueRange> getConvertedMapOpGroups(Output out) {
1742 return llvm::map_to_vector(out.mapOpGroups, [](const SmallVector<Value> &grp) {
1743 return ValueRange(grp);
1744 });
1745 }
1746
1747 static LogicalResult
1748 fold(PatternRewriter &rewriter, const Input &in, Output &out, Operation *op, const char *aspect) {
1749 if (in.mapOpGroups.empty()) {
1750 // No affine map operands so nothing to do
1751 return failure();
1752 }
1753
1754 assert(in.mapOpGroups.size() <= in.paramsOfStructTy.size());
1755 assert(std::cmp_equal(in.mapOpGroups.size(), in.dimsPerGroup.size()));
1756
1757 size_t idx = 0; // index in `mapOpGroups`, i.e., the number of AffineMapAttr encountered
1758 for (Attribute sizeAttr : in.paramsOfStructTy) {
1759 if (AffineMapAttr m = dyn_cast<AffineMapAttr>(sizeAttr)) {
1760 ValueRange currMapOps = in.mapOpGroups[idx++];
1761 LLVM_DEBUG(
1762 llvm::dbgs() << "[AffineMapFolder] currMapOps: " << debug::toStringList(currMapOps)
1763 << '\n'
1764 );
1765 SmallVector<OpFoldResult> currMapOpsCast = getAsOpFoldResult(currMapOps);
1766 LLVM_DEBUG(
1767 llvm::dbgs() << "[AffineMapFolder] currMapOps as fold results: "
1768 << debug::toStringList(currMapOpsCast) << '\n'
1769 );
1770 if (auto constOps = Step4_InstantiateAffineMaps::getConstantIntValues(currMapOpsCast)) {
1771 SmallVector<Attribute> result;
1772 bool hasPoison = false; // indicates divide by 0 or mod by <1
1773 auto constAttrs = llvm::map_to_vector(*constOps, [&rewriter](int64_t v) -> Attribute {
1774 return rewriter.getIndexAttr(v);
1775 });
1776 LogicalResult foldResult = m.getAffineMap().constantFold(constAttrs, result, &hasPoison);
1777 if (hasPoison) {
1778 // Diagnostic remark: could be removed for release builds if too noisy
1779 op->emitRemark()
1780 .append(
1781 "Cannot fold affine_map for ", aspect, ' ', out.paramsOfStructTy.size(),
1782 " due to divide by 0 or modulus with negative divisor"
1783 )
1784 .report();
1785 return failure();
1786 }
1787 if (failed(foldResult)) {
1788 // Diagnostic remark: could be removed for release builds if too noisy
1789 op->emitRemark()
1790 .append(
1791 "Folding affine_map for ", aspect, ' ', out.paramsOfStructTy.size(), " failed"
1792 )
1793 .report();
1794 return failure();
1795 }
1796 if (result.size() != 1) {
1797 // Diagnostic remark: could be removed for release builds if too noisy
1798 op->emitRemark()
1799 .append(
1800 "Folding affine_map for ", aspect, ' ', out.paramsOfStructTy.size(),
1801 " produced ", result.size(), " results but expected 1"
1802 )
1803 .report();
1804 return failure();
1805 }
1806 assert(!llvm::isa<AffineMapAttr>(result[0]) && "not converted");
1807 out.paramsOfStructTy.push_back(result[0]);
1808 continue;
1809 }
1810 // If affine but not foldable, preserve the map ops
1811 out.mapOpGroups.emplace_back(currMapOps);
1812 out.dimsPerGroup.push_back(in.dimsPerGroup[idx - 1]); // idx was already incremented
1813 }
1814 // If not affine and foldable, preserve the original
1815 out.paramsOfStructTy.push_back(sizeAttr);
1816 }
1817 assert(idx == in.mapOpGroups.size() && "all affine_map not processed");
1818 assert(
1819 in.paramsOfStructTy.size() == out.paramsOfStructTy.size() &&
1820 "produced wrong number of dimensions"
1821 );
1822
1823 return success();
1824 }
1825};
1826
1828class InstantiateAtCreateArrayOp final : public OpRewritePattern<CreateArrayOp> {
1829 [[maybe_unused]]
1830 ConversionTracker &tracker_;
1831
1832public:
1833 InstantiateAtCreateArrayOp(MLIRContext *ctx, ConversionTracker &tracker)
1834 : OpRewritePattern(ctx), tracker_(tracker) {}
1835
1836 LogicalResult matchAndRewrite(CreateArrayOp op, PatternRewriter &rewriter) const override {
1837 ArrayType oldResultType = op.getType();
1838
1839 AffineMapFolder::Output out;
1840 AffineMapFolder::Input in = {
1841 op.getMapOperands(),
1843 oldResultType.getDimensionSizes(),
1844 };
1845 if (failed(AffineMapFolder::fold(rewriter, in, out, op, "array dimension"))) {
1846 return failure();
1847 }
1848
1849 ArrayType newResultType = ArrayType::get(oldResultType.getElementType(), out.paramsOfStructTy);
1850 if (newResultType == oldResultType) {
1851 return failure(); // nothing changed
1852 }
1853 // ASSERT: folding only preserves the original Attribute or converts affine to integer
1854 assert(tracker_.isLegalConversion(oldResultType, newResultType, "InstantiateAtCreateArrayOp"));
1855 LLVM_DEBUG(
1856 llvm::dbgs() << "[InstantiateAtCreateArrayOp] instantiating " << oldResultType << " as "
1857 << newResultType << " in \"" << op << "\"\n"
1858 );
1860 rewriter, op, newResultType, AffineMapFolder::getConvertedMapOpGroups(out), out.dimsPerGroup
1861 );
1862 return success();
1863 }
1864};
1865
1867class InstantiateAtCallOpCompute final : public OpRewritePattern<CallOp> {
1868 ConversionTracker &tracker_;
1869
1870public:
1871 InstantiateAtCallOpCompute(MLIRContext *ctx, ConversionTracker &tracker)
1872 : OpRewritePattern(ctx), tracker_(tracker) {}
1873
1874 LogicalResult matchAndRewrite(CallOp op, PatternRewriter &rewriter) const override {
1875 if (!op.calleeIsStructCompute()) {
1876 // this pattern only applies when the callee is "compute()" within a struct
1877 return failure();
1878 }
1879 LLVM_DEBUG(llvm::dbgs() << "[InstantiateAtCallOpCompute] target: " << op.getCallee() << '\n');
1881 LLVM_DEBUG(llvm::dbgs() << "[InstantiateAtCallOpCompute] oldRetTy: " << oldRetTy << '\n');
1882 ArrayAttr params = oldRetTy.getParams();
1883 if (isNullOrEmpty(params)) {
1884 // nothing to do if the StructType is not parameterized
1885 return failure();
1886 }
1887
1888 AffineMapFolder::Output out;
1889 AffineMapFolder::Input in = {
1890 op.getMapOperands(),
1892 params.getValue(),
1893 };
1894 if (!in.mapOpGroups.empty()) {
1895 // If there are affine map operands, attempt to fold them to a constant.
1896 if (failed(AffineMapFolder::fold(rewriter, in, out, op, "struct parameter"))) {
1897 return failure();
1898 }
1899 LLVM_DEBUG({
1900 llvm::dbgs() << "[InstantiateAtCallOpCompute] folded affine_map in result type params\n";
1901 });
1902 } else {
1903 // If there are no affine map operands, attempt to refine the result type of the CallOp using
1904 // the function argument types and the type of the target function.
1905 auto callArgTypes = op.getArgOperands().getTypes();
1906 if (callArgTypes.empty()) {
1907 // no refinement possible if no function arguments
1908 return failure();
1909 }
1910 if (calleeReferencesTemplateParam(op)) {
1911 return failure();
1912 }
1913 SymbolTableCollection tables;
1914 auto lookupRes = lookupTopLevelSymbol<FuncDefOp>(tables, op.getCalleeAttr(), op);
1915 if (failed(lookupRes)) {
1916 return failure();
1917 }
1918 if (failed(instantiateViaTargetType(in, out, callArgTypes, lookupRes->get()))) {
1919 return failure();
1920 }
1921 LLVM_DEBUG({
1922 llvm::dbgs() << "[InstantiateAtCallOpCompute] propagated instantiations via symrefs in "
1923 "result type params: "
1924 << debug::toStringList(out.paramsOfStructTy) << '\n';
1925 });
1926 }
1927
1928 StructType newRetTy = StructType::get(oldRetTy.getNameRef(), out.paramsOfStructTy);
1929 LLVM_DEBUG(llvm::dbgs() << "[InstantiateAtCallOpCompute] newRetTy: " << newRetTy << '\n');
1930 if (newRetTy == oldRetTy) {
1931 return failure(); // nothing changed
1932 }
1933 // The `newRetTy` is computed via instantiateViaTargetType() which can only preserve the
1934 // original Attribute or convert to a concrete attribute via the unification process. Thus, if
1935 // the conversion here is illegal it means there is a type conflict within the LLZK code that
1936 // prevents instantiation of the struct with the requested type.
1937 if (!tracker_.isLegalConversion(oldRetTy, newRetTy, "InstantiateAtCallOpCompute")) {
1938 return rewriter.notifyMatchFailure(op, [&](Diagnostic &diag) {
1939 diag.append(
1940 "result type mismatch: due to struct instantiation, expected type ", newRetTy,
1941 ", but found ", oldRetTy
1942 );
1943 });
1944 }
1945 LLVM_DEBUG(llvm::dbgs() << "[InstantiateAtCallOpCompute] replaced " << op);
1947 rewriter, op, TypeRange {newRetTy}, op.getCallee(),
1948 AffineMapFolder::getConvertedMapOpGroups(out), out.dimsPerGroup, op.getArgOperands()
1949 );
1950 (void)newOp; // tell compiler it's intentionally unused in release builds
1951 LLVM_DEBUG(llvm::dbgs() << " with " << newOp << '\n');
1952 return success();
1953 }
1954
1955private:
1958 inline LogicalResult instantiateViaTargetType(
1959 const AffineMapFolder::Input &in, AffineMapFolder::Output &out,
1960 OperandRange::type_range callArgTypes, FuncDefOp targetFunc
1961 ) const {
1962 assert(targetFunc.isStructCompute()); // since `op.calleeIsStructCompute()`
1963 ArrayAttr targetResTyParams = targetFunc.getSingleResultTypeOfCompute().getParams();
1964 assert(!isNullOrEmpty(targetResTyParams)); // same cardinality as `in.paramsOfStructTy`
1965 assert(in.paramsOfStructTy.size() == targetResTyParams.size()); // verifier ensures this
1966
1967 if (llvm::all_of(in.paramsOfStructTy, isConcreteAttr<>)) {
1968 // Nothing can change if everything is already concrete
1969 return failure();
1970 }
1971
1972 LLVM_DEBUG({
1973 llvm::dbgs() << '[' << __FUNCTION__ << ']'
1974 << " call arg types: " << debug::toStringList(callArgTypes) << '\n';
1975 llvm::dbgs() << '[' << __FUNCTION__ << ']' << " target func arg types: "
1976 << debug::toStringList(targetFunc.getArgumentTypes()) << '\n';
1977 llvm::dbgs() << '[' << __FUNCTION__ << ']'
1978 << " struct params @ call: " << debug::toStringList(in.paramsOfStructTy) << '\n';
1979 llvm::dbgs() << '[' << __FUNCTION__ << ']'
1980 << " target struct params: " << debug::toStringList(targetResTyParams) << '\n';
1981 });
1982
1983 UnificationMap unifications;
1984 bool unifies = typeListsUnify(targetFunc.getArgumentTypes(), callArgTypes, {}, &unifications);
1985 (void)unifies; // tell compiler it's intentionally unused in builds without assertions
1986 assert(unifies && "should have been checked by verifiers");
1987
1988 LLVM_DEBUG({
1989 llvm::dbgs() << '[' << __FUNCTION__ << ']'
1990 << " unifications of arg types: " << debug::toStringList(unifications) << '\n';
1991 });
1992
1993 // Check for LHS SymRef (i.e., from the target function) that have RHS concrete Attributes (i.e.
1994 // from the call argument types) without any struct parameters (because the type with concrete
1995 // struct parameters will be used to instantiate the target struct rather than the fully
1996 // flattened struct type resulting in type mismatch of the callee to target) and perform those
1997 // replacements in the `targetFunc` return type to produce the new result type for the CallOp.
1998 SmallVector<Attribute> newReturnStructParams = llvm::map_to_vector(
1999 llvm::zip_equal(targetResTyParams.getValue(), in.paramsOfStructTy),
2000 [&unifications](std::tuple<Attribute, Attribute> p) {
2001 Attribute fromCall = std::get<1>(p);
2002 // Preserve attributes that are already concrete at the call site. Otherwise attempt to lookup
2003 // non-parameterized concrete unification for the target struct parameter symbol.
2004 if (!isConcreteAttr(fromCall)) {
2005 Attribute fromTgt = std::get<0>(p);
2006 LLVM_DEBUG({
2007 llvm::dbgs() << "[instantiateViaTargetType] fromCall = " << fromCall << '\n';
2008 llvm::dbgs() << "[instantiateViaTargetType] fromTgt = " << fromTgt << '\n';
2009 });
2010 assert(llvm::isa<SymbolRefAttr>(fromTgt));
2011 auto it = unifications.find(std::make_pair(llvm::cast<SymbolRefAttr>(fromTgt), Side::LHS));
2012 if (it != unifications.end()) {
2013 Attribute unifiedAttr = it->second;
2014 LLVM_DEBUG({
2015 llvm::dbgs() << "[instantiateViaTargetType] unifiedAttr = " << unifiedAttr << '\n';
2016 });
2017 if (unifiedAttr && isConcreteAttr<false>(unifiedAttr)) {
2018 return unifiedAttr;
2019 }
2020 }
2021 }
2022 return fromCall;
2023 }
2024 );
2025
2026 out.paramsOfStructTy = newReturnStructParams;
2027 assert(out.paramsOfStructTy.size() == in.paramsOfStructTy.size() && "post-condition");
2028 assert(out.mapOpGroups.empty() && "post-condition");
2029 assert(out.dimsPerGroup.empty() && "post-condition");
2030 return success();
2031 }
2032};
2033
2034LogicalResult run(ModuleOp modOp, ConversionTracker &tracker) {
2035 MLIRContext *ctx = modOp.getContext();
2036 RewritePatternSet patterns(ctx);
2037 patterns.add<
2038 InstantiateAtCreateArrayOp, // CreateArrayOp
2039 InstantiateAtCallOpCompute // CallOp, targeting struct "compute()"
2040 >(ctx, tracker);
2041
2042 return applyAndFoldGreedily(modOp, tracker, std::move(patterns));
2043}
2044
2045} // namespace Step4_InstantiateAffineMaps
2046
2048
2050class UpdateNewArrayElemFromWrite final : public OpRewritePattern<CreateArrayOp> {
2051 ConversionTracker &tracker_;
2052
2053public:
2054 UpdateNewArrayElemFromWrite(MLIRContext *ctx, ConversionTracker &tracker)
2055 : OpRewritePattern(ctx, 3), tracker_(tracker) {}
2056
2057 LogicalResult matchAndRewrite(CreateArrayOp op, PatternRewriter &rewriter) const override {
2058 Value createResult = op.getResult();
2059 ArrayType createResultType = dyn_cast<ArrayType>(createResult.getType());
2060 assert(createResultType && "CreateArrayOp must produce ArrayType");
2061 Type oldResultElemType = createResultType.getElementType();
2062
2063 // Look for WriteArrayOp where the array reference is the result of the CreateArrayOp and the
2064 // element type is different.
2065 Type newResultElemType = nullptr;
2066 for (Operation *user : createResult.getUsers()) {
2067 if (WriteArrayOp writeOp = dyn_cast<WriteArrayOp>(user)) {
2068 if (writeOp.getArrRef() != createResult) {
2069 continue;
2070 }
2071 Type writeRValueType = writeOp.getRvalue().getType();
2072 if (writeRValueType == oldResultElemType) {
2073 continue;
2074 }
2075 if (newResultElemType && newResultElemType != writeRValueType) {
2076 LLVM_DEBUG(
2077 llvm::dbgs()
2078 << "[UpdateNewArrayElemFromWrite] multiple possible element types for CreateArrayOp "
2079 << newResultElemType << " vs " << writeRValueType << '\n'
2080 );
2081 return failure();
2082 }
2083 newResultElemType = writeRValueType;
2084 }
2085 }
2086 if (!newResultElemType) {
2087 // no replacement type found
2088 return failure();
2089 }
2090 if (!tracker_.isLegalConversion(
2091 oldResultElemType, newResultElemType, "UpdateNewArrayElemFromWrite"
2092 )) {
2093 return failure();
2094 }
2095 ArrayType newType = createResultType.cloneWith(newResultElemType);
2096 rewriter.modifyOpInPlace(op, [&createResult, &newType]() { createResult.setType(newType); });
2097 LLVM_DEBUG(
2098 llvm::dbgs() << "[UpdateNewArrayElemFromWrite] updated result type of " << op << '\n'
2099 );
2100 return success();
2101 }
2102};
2103
2104namespace {
2105
2106LogicalResult updateArrayElemFromArrAccessOp(
2107 ArrayAccessOpInterface op, Type scalarElemTy, ConversionTracker &tracker,
2108 PatternRewriter &rewriter
2109) {
2110 ArrayType oldArrType = op.getArrRefType();
2111 if (oldArrType.getElementType() == scalarElemTy) {
2112 return failure(); // no change needed
2113 }
2114 ArrayType newArrType = oldArrType.cloneWith(scalarElemTy);
2115 if (oldArrType == newArrType ||
2116 !tracker.isLegalConversion(oldArrType, newArrType, "updateArrayElemFromArrAccessOp")) {
2117 return failure();
2118 }
2119 rewriter.modifyOpInPlace(op, [&op, &newArrType]() { op.getArrRef().setType(newArrType); });
2120 LLVM_DEBUG(
2121 llvm::dbgs() << "[updateArrayElemFromArrAccessOp] updated base array type in " << op << '\n'
2122 );
2123 return success();
2124}
2125
2126} // namespace
2127
2128class UpdateArrayElemFromArrWrite final : public OpRewritePattern<WriteArrayOp> {
2129 ConversionTracker &tracker_;
2130
2131public:
2132 UpdateArrayElemFromArrWrite(MLIRContext *ctx, ConversionTracker &tracker)
2133 : OpRewritePattern(ctx, 3), tracker_(tracker) {}
2134
2135 LogicalResult matchAndRewrite(WriteArrayOp op, PatternRewriter &rewriter) const override {
2136 return updateArrayElemFromArrAccessOp(op, op.getRvalue().getType(), tracker_, rewriter);
2137 }
2138};
2139
2140class UpdateArrayElemFromArrRead final : public OpRewritePattern<ReadArrayOp> {
2141 ConversionTracker &tracker_;
2142
2143public:
2144 UpdateArrayElemFromArrRead(MLIRContext *ctx, ConversionTracker &tracker)
2145 : OpRewritePattern(ctx, 3), tracker_(tracker) {}
2146
2147 LogicalResult matchAndRewrite(ReadArrayOp op, PatternRewriter &rewriter) const override {
2148 return updateArrayElemFromArrAccessOp(op, op.getResult().getType(), tracker_, rewriter);
2149 }
2150};
2151
2153class UpdateMemberDefTypeFromWrite final : public OpRewritePattern<MemberDefOp> {
2154 ConversionTracker &tracker_;
2155
2156public:
2157 UpdateMemberDefTypeFromWrite(MLIRContext *ctx, ConversionTracker &tracker)
2158 : OpRewritePattern(ctx, 3), tracker_(tracker) {}
2159
2160 LogicalResult matchAndRewrite(MemberDefOp op, PatternRewriter &rewriter) const override {
2161 // Find all uses of the member symbol name within its parent struct.
2163 assert(parentRes && "MemberDefOp parent is always StructDefOp"); // per ODS def
2164
2165 // If the symbol is used by a MemberWriteOp with a different result type then change
2166 // the type of the MemberDefOp to match the MemberWriteOp result type.
2167 Type newType = nullptr;
2168 if (auto memberUsers = llzk::getSymbolUses(op, parentRes)) {
2169 std::optional<Location> newTypeLoc = std::nullopt;
2170 for (SymbolTable::SymbolUse symUse : memberUsers.value()) {
2171 if (MemberWriteOp writeOp = llvm::dyn_cast<MemberWriteOp>(symUse.getUser())) {
2172 Type writeToType = writeOp.getVal().getType();
2173 LLVM_DEBUG(llvm::dbgs() << "[UpdateMemberDefTypeFromWrite] checking " << writeOp << '\n');
2174 if (!newType) {
2175 // If a new type has not yet been discovered, store the new type.
2176 newType = writeToType;
2177 newTypeLoc = writeOp.getLoc();
2178 } else if (writeToType != newType) {
2179 // Typically, there will only be one write for each member of a struct but do not rely
2180 // on that assumption. If multiple writes with a different types A and B are found where
2181 // A->B is a legal conversion (i.e., more concrete unification), then it is safe to use
2182 // type B with the assumption that the write with type A will be updated by another
2183 // pattern to also use type B.
2184 if (!tracker_.isLegalConversion(writeToType, newType, "UpdateMemberDefTypeFromWrite")) {
2185 if (tracker_.isLegalConversion(
2186 newType, writeToType, "UpdateMemberDefTypeFromWrite"
2187 )) {
2188 // 'writeToType' is the more concrete type
2189 newType = writeToType;
2190 newTypeLoc = writeOp.getLoc();
2191 } else {
2192 // Give an error if the types are incompatible.
2193 return rewriter.notifyMatchFailure(op, [&](Diagnostic &diag) {
2194 diag.append(
2195 "Cannot update type of '", MemberDefOp::getOperationName(),
2196 "' because there are multiple '", MemberWriteOp::getOperationName(),
2197 "' with different value types"
2198 );
2199 if (newTypeLoc) {
2200 diag.attachNote(newTypeLoc).append("type written here is ", newType);
2201 }
2202 diag.attachNote(writeOp.getLoc()).append("type written here is ", writeToType);
2203 });
2204 }
2205 }
2206 }
2207 }
2208 }
2209 }
2210 if (!newType || newType == op.getType()) {
2211 return failure(); // nothing changed
2212 }
2213 if (!tracker_.isLegalConversion(op.getType(), newType, "UpdateMemberDefTypeFromWrite")) {
2214 return failure();
2215 }
2216 rewriter.modifyOpInPlace(op, [&op, &newType]() { op.setType(newType); });
2217 LLVM_DEBUG(llvm::dbgs() << "[UpdateMemberDefTypeFromWrite] updated type of " << op << '\n');
2218 return success();
2219 }
2220};
2221
2222namespace {
2223
2224SmallVector<std::unique_ptr<Region>> moveRegions(Operation *op) {
2225 SmallVector<std::unique_ptr<Region>> newRegions;
2226 for (Region &region : op->getRegions()) {
2227 auto newRegion = std::make_unique<Region>();
2228 newRegion->takeBody(region);
2229 newRegions.push_back(std::move(newRegion));
2230 }
2231 return newRegions;
2232}
2233
2234} // namespace
2235
2238class UpdateInferredResultTypes final : public OpTraitRewritePattern<OpTrait::InferTypeOpAdaptor> {
2239 ConversionTracker &tracker_;
2240
2241public:
2242 UpdateInferredResultTypes(MLIRContext *ctx, ConversionTracker &tracker)
2243 : OpTraitRewritePattern(ctx, 6), tracker_(tracker) {}
2244
2245 LogicalResult matchAndRewrite(Operation *op, PatternRewriter &rewriter) const override {
2246 SmallVector<Type, 1> inferredResultTypes;
2247 InferTypeOpInterface retTypeFn = llvm::cast<InferTypeOpInterface>(op);
2248 LogicalResult result = retTypeFn.inferReturnTypes(
2249 op->getContext(), op->getLoc(), op->getOperands(), op->getRawDictionaryAttrs(),
2250 op->getPropertiesStorage(), op->getRegions(), inferredResultTypes
2251 );
2252 if (failed(result)) {
2253 return failure();
2254 }
2255 if (op->getResultTypes() == inferredResultTypes) {
2256 return failure(); // nothing changed
2257 }
2258 if (!tracker_.areLegalConversions(
2259 op->getResultTypes(), inferredResultTypes, "UpdateInferredResultTypes"
2260 )) {
2261 return failure();
2262 }
2263
2264 // Move nested region bodies and replace the original op with the updated types list.
2265 LLVM_DEBUG(llvm::dbgs() << "[UpdateInferredResultTypes] replaced " << *op);
2266 SmallVector<std::unique_ptr<Region>> newRegions = moveRegions(op);
2267 Operation *newOp = rewriter.create(
2268 op->getLoc(), op->getName().getIdentifier(), op->getOperands(), inferredResultTypes,
2269 op->getAttrs(), op->getSuccessors(), newRegions
2270 );
2271 rewriter.replaceOp(op, newOp);
2272 LLVM_DEBUG(llvm::dbgs() << " with " << *newOp << '\n');
2273 return success();
2274 }
2275};
2276
2278class UpdateFuncTypeFromReturn final : public OpRewritePattern<FuncDefOp> {
2279 ConversionTracker &tracker_;
2280
2281public:
2282 UpdateFuncTypeFromReturn(MLIRContext *ctx, ConversionTracker &tracker)
2283 : OpRewritePattern(ctx, 3), tracker_(tracker) {}
2284
2285 LogicalResult matchAndRewrite(FuncDefOp op, PatternRewriter &rewriter) const override {
2286 Region &body = op.getFunctionBody();
2287 if (body.empty()) {
2288 return failure();
2289 }
2290 ReturnOp retOp = llvm::dyn_cast<ReturnOp>(body.back().getTerminator());
2291 assert(retOp && "final op in body region must be return");
2292 OperandRange::type_range tyFromReturnOp = retOp.getOperands().getTypes();
2293
2294 FunctionType oldFuncTy = op.getFunctionType();
2295 if (oldFuncTy.getResults() == tyFromReturnOp) {
2296 return failure(); // nothing changed
2297 }
2298 if (!tracker_.areLegalConversions(
2299 oldFuncTy.getResults(), tyFromReturnOp, "UpdateFuncTypeFromReturn"
2300 )) {
2301 return failure();
2302 }
2303
2304 rewriter.modifyOpInPlace(op, [&]() {
2305 op.setFunctionType(rewriter.getFunctionType(oldFuncTy.getInputs(), tyFromReturnOp));
2306 });
2307 LLVM_DEBUG(
2308 llvm::dbgs() << "[UpdateFuncTypeFromReturn] changed " << op.getSymName() << " from "
2309 << oldFuncTy << " to " << op.getFunctionType() << '\n'
2310 );
2311 return success();
2312 }
2313};
2314
2319class UpdateFreeFuncCallOpTypes final : public OpRewritePattern<CallOp> {
2320 ConversionTracker &tracker_;
2321
2322public:
2323 UpdateFreeFuncCallOpTypes(MLIRContext *ctx, ConversionTracker &tracker)
2324 : OpRewritePattern(ctx, 3), tracker_(tracker) {}
2325
2326 LogicalResult matchAndRewrite(CallOp op, PatternRewriter &rewriter) const override {
2327 if (calleeReferencesTemplateParam(op)) {
2328 return failure();
2329 }
2330 SymbolTableCollection tables;
2331 auto lookupRes = lookupTopLevelSymbol<FuncDefOp>(tables, op.getCalleeAttr(), op);
2332 if (failed(lookupRes)) {
2333 return failure();
2334 }
2335 FuncDefOp targetFunc = lookupRes->get();
2336 if (targetFunc.isInStruct()) {
2337 // this pattern only applies when the callee is NOT in a struct
2338 return failure();
2339 }
2340 if (op.getResultTypes() == targetFunc.getFunctionType().getResults()) {
2341 return failure(); // nothing changed
2342 }
2343 if (!tracker_.areLegalConversions(
2344 op.getResultTypes(), targetFunc.getFunctionType().getResults(),
2345 "UpdateFreeFuncCallOpTypes"
2346 )) {
2347 return failure();
2348 }
2349
2350 LLVM_DEBUG(llvm::dbgs() << "[UpdateFreeFuncCallOpTypes] replaced " << op);
2351 CallOp newOp = replaceOpWithNewOp<CallOp>(rewriter, op, targetFunc, op.getArgOperands());
2352 (void)newOp; // tell compiler it's intentionally unused in release builds
2353 LLVM_DEBUG(llvm::dbgs() << " with " << newOp << '\n');
2354 return success();
2355 }
2356};
2357
2358namespace {
2359
2360LogicalResult updateMemberRefValFromMemberDef(
2361 MemberRefOpInterface op, ConversionTracker &tracker, PatternRewriter &rewriter
2362) {
2363 SymbolTableCollection tables;
2364 auto def = op.getMemberDefOp(tables);
2365 if (failed(def)) {
2366 return failure();
2367 }
2368 Type oldResultType = op.getVal().getType();
2369 Type newResultType = def->get().getType();
2370 if (oldResultType == newResultType ||
2371 !tracker.isLegalConversion(oldResultType, newResultType, "updateMemberRefValFromMemberDef")) {
2372 return failure();
2373 }
2374 rewriter.modifyOpInPlace(op, [&op, &newResultType]() { op.getVal().setType(newResultType); });
2375 LLVM_DEBUG(
2376 llvm::dbgs() << "[updateMemberRefValFromMemberDef] updated value type in " << op << '\n'
2377 );
2378 return success();
2379}
2380
2381} // namespace
2382
2384class UpdateMemberReadValFromDef final : public OpRewritePattern<MemberReadOp> {
2385 ConversionTracker &tracker_;
2386
2387public:
2388 UpdateMemberReadValFromDef(MLIRContext *ctx, ConversionTracker &tracker)
2389 : OpRewritePattern(ctx, 3), tracker_(tracker) {}
2390
2391 LogicalResult matchAndRewrite(MemberReadOp op, PatternRewriter &rewriter) const override {
2392 return updateMemberRefValFromMemberDef(op, tracker_, rewriter);
2393 }
2394};
2395
2397class UpdateMemberWriteValFromDef final : public OpRewritePattern<MemberWriteOp> {
2398 ConversionTracker &tracker_;
2399
2400public:
2401 UpdateMemberWriteValFromDef(MLIRContext *ctx, ConversionTracker &tracker)
2402 : OpRewritePattern(ctx, 3), tracker_(tracker) {}
2403
2404 LogicalResult matchAndRewrite(MemberWriteOp op, PatternRewriter &rewriter) const override {
2405 return updateMemberRefValFromMemberDef(op, tracker_, rewriter);
2406 }
2407};
2408
2409LogicalResult run(ModuleOp modOp, ConversionTracker &tracker) {
2410 MLIRContext *ctx = modOp.getContext();
2411 RewritePatternSet patterns(ctx);
2412 patterns.add<
2413 // Benefit of this one must be higher than rules that would propagate the type in the opposite
2414 // direction (ex: `UpdateArrayElemFromArrRead`) else the greedy conversion would not converge.
2415 // benefit = 6
2416 UpdateInferredResultTypes, // OpTrait::InferTypeOpAdaptor (ReadArrayOp, ExtractArrayOp)
2417 // benefit = 3
2418 UpdateFreeFuncCallOpTypes, // CallOp, targeting non-struct functions
2419 UpdateFuncTypeFromReturn, // FuncDefOp
2420 UpdateNewArrayElemFromWrite, // CreateArrayOp
2421 UpdateArrayElemFromArrRead, // ReadArrayOp
2422 UpdateArrayElemFromArrWrite, // WriteArrayOp
2423 UpdateMemberDefTypeFromWrite, // MemberDefOp
2424 UpdateMemberReadValFromDef, // MemberReadOp
2425 UpdateMemberWriteValFromDef // MemberWriteOp
2426 >(ctx, tracker);
2427
2428 return applyAndFoldGreedily(modOp, tracker, std::move(patterns));
2429}
2430} // namespace Step5_PropagateTypes
2431
2432namespace Step6_Cleanup {
2433
2434struct FromKeepSet : public CleanupBase {
2436
2439 static bool hasTemplateSymbolBindings(Operation *op) {
2440 if (StructDefOp sdef = llvm::dyn_cast<StructDefOp>(op)) {
2441 return sdef.hasTemplateSymbolBindings();
2442 }
2443 if (llvm::isa<function::FuncDefOp>(op)) {
2444 if (TemplateOp parent = getParentOfType<TemplateOp>(op)) {
2445 return parent.hasConstOps<TemplateSymbolBindingOpInterface>();
2446 }
2447 }
2448 return false;
2449 }
2450
2454 LogicalResult eraseUnreachableFrom(ArrayRef<SymbolOpInterface> keep) {
2455 // Initialize roots from the given symbol definitions.
2456 SetVector<SymbolOpInterface> roots(keep.begin(), keep.end());
2457 // Add GlobalDefOp to the set of roots.
2458 rootMod.walk([&roots](global::GlobalDefOp gdef) { roots.insert(gdef); });
2459
2460 // Use a SymbolDefTree to find all Symbol defs reachable from one of the root nodes. Then
2461 // collect all Symbol uses reachable from those def nodes. These are the symbols that should
2462 // be preserved. All other symbol defs should be removed.
2463 DenseSet<Operation *> defsToKeep;
2464 llvm::df_iterator_default_set<const SymbolUseGraphNode *> symbolsToKeep;
2465 for (size_t i = 0; i < roots.size(); ++i) { // iterate for safe insertion
2466 SymbolOpInterface keepRoot = roots[i];
2467 LLVM_DEBUG({ llvm::dbgs() << "[EraseUnreachable] root: " << keepRoot << '\n'; });
2468 const SymbolDefTreeNode *keepRootNode = defTree.lookupNode(keepRoot);
2469 assert(keepRootNode && "every symbol def must be in the def tree");
2470 for (const SymbolDefTreeNode *reachableDefNode : llvm::depth_first(keepRootNode)) {
2471 LLVM_DEBUG({
2472 llvm::dbgs() << "[EraseUnreachable] can reach: " << reachableDefNode->getOp() << '\n';
2473 });
2474 if (SymbolOpInterface reachableDef = reachableDefNode->getOp()) {
2475 if (isErasableDefinition(reachableDef.getOperation())) {
2476 defsToKeep.insert(reachableDef.getOperation());
2477 }
2478 // Use 'depth_first_ext()' to get all symbol uses reachable from the current Symbol def
2479 // node. There are no uses if the node is not in the graph. Within the loop that populates
2480 // 'depth_first_ext()', also check if the symbol is an erasable definition and ensure it
2481 // is in 'roots' so the outer loop preserves all symbols reachable from it.
2482 if (const SymbolUseGraphNode *useGraphNodeForDef = useGraph.lookupNode(reachableDef)) {
2483 for (const SymbolUseGraphNode *usedSymbolNode :
2484 depth_first_ext(useGraphNodeForDef, symbolsToKeep)) {
2485 LLVM_DEBUG({
2486 llvm::dbgs() << "[EraseUnreachable] uses symbol: "
2487 << usedSymbolNode->getSymbolPath() << '\n';
2488 });
2489 // Ignore struct/template parameter symbols (before doing the lookup below because it
2490 // would fail anyway and then cause the "failed" case to be triggered unnecessarily).
2491 if (usedSymbolNode->isTemplateSymbolBinding()) {
2492 continue;
2493 }
2494 // If `usedSymbolNode` references an erasable definition, ensure it's considered in
2495 // the roots so symbols reachable from its body are preserved too.
2496 auto lookupRes = usedSymbolNode->lookupSymbol(tables);
2497 if (failed(lookupRes)) {
2498 LLVM_DEBUG(useGraph.dumpToDotFile());
2499 return failure();
2500 }
2501 // If loaded via an IncludeOp it's not in the current AST anyway so ignore.
2502 if (lookupRes->viaInclude()) {
2503 continue;
2504 }
2505 Operation *usedOp = lookupRes->get();
2506 if (isErasableDefinition(usedOp)) {
2507 SymbolOpInterface asSymbol = llvm::cast<SymbolOpInterface>(usedOp);
2508 bool insertRes = roots.insert(asSymbol);
2509 (void)insertRes; // tell compiler it's intentionally unused in release builds
2510 LLVM_DEBUG({
2511 if (insertRes) {
2512 llvm::dbgs() << "[EraseUnreachable] found another root: " << asSymbol << '\n';
2513 }
2514 });
2515 }
2516 }
2517 }
2518 }
2519 }
2520 }
2521
2522 SmallVector<SymbolOpInterface> toErase;
2523 rootMod.walk([this, &defsToKeep, &symbolsToKeep, &toErase](Operation *op) {
2524 if (!isErasableDefinition(op) || defsToKeep.contains(op)) {
2525 return;
2526 }
2527 SymbolOpInterface symOp = llvm::cast<SymbolOpInterface>(op);
2528 const SymbolUseGraphNode *n = this->useGraph.lookupNode(symOp);
2529 if (!n || !symbolsToKeep.contains(n)) {
2530 LLVM_DEBUG(llvm::dbgs() << "[EraseUnreachable] removing: " << symOp.getNameAttr() << '\n');
2531 toErase.push_back(symOp);
2532 }
2533 });
2534 for (SymbolOpInterface symOp : toErase) {
2535 symOp.erase();
2536 }
2537
2538 return success();
2539 }
2540};
2541
2542} // namespace Step6_Cleanup
2543
2544class PassImpl : public llzk::polymorphic::impl::FlatteningPassBase<PassImpl> {
2545 using Base = FlatteningPassBase<PassImpl>;
2546 using Base::Base;
2547
2549 FlatteningCleanupMode getEffectiveCleanupMode() const {
2550 FlatteningCleanupMode m = cleanupMode.getValue();
2552 }
2553
2554 void runOnOperation() override {
2555 ModuleOp modOp = getOperation();
2556 if (failed(runOn(modOp))) {
2557 LLVM_DEBUG({
2558 // If the pass failed, dump the current IR.
2559 llvm::dbgs() << "=====================================================================\n";
2560 llvm::dbgs() << " Dumping module after failure of pass " << DEBUG_TYPE << '\n';
2561 modOp.print(llvm::dbgs(), OpPrintingFlags().assumeVerified());
2562 llvm::dbgs() << "=====================================================================\n";
2563 });
2564 signalPassFailure();
2565 }
2566 }
2567
2568 inline LogicalResult runOn(ModuleOp modOp) {
2569 FlatteningCleanupMode effectiveCleanupMode = getEffectiveCleanupMode();
2570 // If the cleanup mode is set to remove anything not reachable from the main struct, do an
2571 // initial pass to remove things that are not reachable (as an optimization) because creating
2572 // an instantiated version of a struct will not cause something to become reachable that was
2573 // not already reachable in parameterized form.
2574 if (effectiveCleanupMode == FlatteningCleanupMode::MainAsRoot) {
2575 if (failed(eraseUnreachableFromMainStruct(modOp))) {
2576 return failure();
2577 }
2578 }
2579
2580 // Pass Manager to run some standard cleanup passes that are always beneficial:
2581 // - Remove templates that contain no struct or function definitions
2582 // - Convert templates with no constant parameters or expressions into modules
2583 OpPassManager universalCleanup(ModuleOp::getOperationName());
2584 universalCleanup.addPass(createEmptyTemplateRemovalPass());
2585
2586 // Run universal cleanup as a preliminary step to satisfy the
2587 // `assert(!isNullOrEmpty(paramNames))` precondition in `genClone()`.
2588 if (failed(runPipeline(universalCleanup, modOp))) {
2589 return failure();
2590 }
2591
2592 ConversionTracker tracker;
2593 if (failed(Step1_InstantiateStructs::instantiateMainStruct(modOp, tracker))) {
2594 llvm::errs() << DEBUG_TYPE << " failed while instantiating the main struct\n";
2595 return failure();
2596 }
2597
2598 unsigned loopCount = 0;
2599 do {
2600 ++loopCount;
2601 if (loopCount > iterationLimit) {
2602 llvm::errs() << DEBUG_TYPE << " exceeded the limit of " << iterationLimit
2603 << " iterations!\n";
2604 return failure();
2605 }
2606 tracker.resetModifiedFlag();
2607
2608 LLVM_DEBUG({
2609 llvm::dbgs() << "[FlatteningPass(count=" << loopCount
2610 << ")] Running step 1: struct instantiation\n";
2611 });
2612 // Find calls to "compute()" that return a parameterized struct type and replace it to call an
2613 // instantiated version of the struct that has parameters replaced with the constant values.
2614 // Create the necessary instantiated/flattened struct in the same location as the original.
2615 if (failed(Step1_InstantiateStructs::run(modOp, tracker))) {
2616 llvm::errs() << DEBUG_TYPE << " failed while instantiating structs in templates\n";
2617 return failure();
2618 }
2619 // Instantiate calls to templated functions.
2620 if (failed(Step2_InstantiateFunctions::run(modOp, tracker))) {
2621 llvm::errs() << DEBUG_TYPE << " failed while instantiating functions in templates\n";
2622 return failure();
2623 }
2624
2625 LLVM_DEBUG({
2626 llvm::dbgs() << "[FlatteningPass(count=" << loopCount
2627 << ")] Running step 2: loop unrolling\n";
2628 });
2629 // Unroll loops with known iterations.
2630 if (failed(Step3_Unroll::run(modOp, tracker))) {
2631 llvm::errs() << DEBUG_TYPE << " failed while unrolling loops\n";
2632 return failure();
2633 }
2634
2635 LLVM_DEBUG({
2636 llvm::dbgs() << "[FlatteningPass(count=" << loopCount
2637 << ")] Running step 3: affine maps instantiation\n";
2638 });
2639 // Instantiate affine_map parameters of StructType and ArrayType.
2640 if (failed(Step4_InstantiateAffineMaps::run(modOp, tracker))) {
2641 llvm::errs() << DEBUG_TYPE << " failed while instantiating `affine_map` parameters\n";
2642 return failure();
2643 }
2644
2645 LLVM_DEBUG({
2646 llvm::dbgs() << "[FlatteningPass(count=" << loopCount
2647 << ")] Running step 4: type propagation\n";
2648 });
2649 // Propagate updated types using the semantics of various ops.
2650 if (failed(Step5_PropagateTypes::run(modOp, tracker))) {
2651 llvm::errs() << DEBUG_TYPE << " failed while propagating instantiated types\n";
2652 return failure();
2653 }
2654
2655 LLVM_DEBUG(if (tracker.isModified()) {
2656 llvm::dbgs() << "=====================================================================\n";
2657 llvm::dbgs() << " Dumping module between iterations of " << DEBUG_TYPE << '\n';
2658 modOp.print(llvm::dbgs(), OpPrintingFlags().assumeVerified());
2659 llvm::dbgs() << "=====================================================================\n";
2660 });
2661 } while (tracker.isModified());
2662
2663 // Run user-selected cleanup first.
2664 if (failed(cleanupSwitch(modOp, tracker))) {
2665 return failure();
2666 }
2667 // Run universal cleanup again since no-param or param-only structs may exist now.
2668 if (failed(runPipeline(universalCleanup, modOp))) {
2669 return failure();
2670 }
2671
2672 OpPassManager allocationCleanup(ModuleOp::getOperationName());
2673 allocationCleanup.addPass(createRemoveUnusedDiscardableAllocationsPass(
2674 RemoveUnusedDiscardableAllocationsPassOptions {
2675 .allocatorOpName = CreateArrayOp::getOperationName().str()
2676 }
2677 ));
2678 return runPipeline(allocationCleanup, modOp);
2679 }
2680
2681 // Perform cleanup according to the 'cleanupMode' option.
2682 LogicalResult cleanupSwitch(ModuleOp modOp, const ConversionTracker &tracker) {
2683 FlatteningCleanupMode effectiveCleanupMode = getEffectiveCleanupMode();
2684 LLVM_DEBUG({ llvm::dbgs() << "[FlatteningPass] Running step 5: cleanup "; });
2685 switch (effectiveCleanupMode) {
2686 case FlatteningCleanupMode::MainAsRoot:
2687 LLVM_DEBUG(llvm::dbgs() << "(main as root mode)\n");
2688 return eraseUnreachableFromMainStruct(modOp, false);
2689 case FlatteningCleanupMode::ConcreteAsRoot:
2690 LLVM_DEBUG(llvm::dbgs() << "(concrete definitions mode)\n");
2691 return eraseUnreachableFromConcreteDefinitions(modOp);
2692 case FlatteningCleanupMode::Preimage:
2693 LLVM_DEBUG(llvm::dbgs() << "(preimage mode)\n");
2694 return erasePreimageOfInstantiations(modOp, tracker);
2695 case FlatteningCleanupMode::Unspecified:
2696 default:
2697 LLVM_DEBUG(llvm::dbgs() << "(disabled)\n");
2698 return success();
2699 }
2700 }
2701
2702 // Erase parameterized definitions that were replaced with concrete instantiations.
2703 LogicalResult erasePreimageOfInstantiations(ModuleOp rootMod, const ConversionTracker &tracker) {
2704 // TODO: The names from getInstantiatedDefinitionNames() are NOT guaranteed to be paths from the
2705 // "top root" and they also do not indicate a root module so there could be ambiguity. This is a
2706 // broader problem in the FlatteningPass itself so let's just assume, for now, that these are
2707 // paths from the "top root". See [LLZK-286].
2708 FromEraseSet cleaner(
2709 rootMod, getAnalysis<SymbolDefTree>(), getAnalysis<SymbolUseGraph>(),
2710 tracker.getInstantiatedDefinitionNames()
2711 );
2712 LogicalResult res = cleaner.eraseUnusedDefinitions();
2713 if (succeeded(res)) {
2714 LLVM_DEBUG(llvm::dbgs() << "[Cleanup(preimage)] success\n";);
2715 // Warn about any definitions that were instantiated but still have uses elsewhere.
2716 const SymbolUseGraph *useGraph = nullptr;
2717 rootMod->walk([this, &cleaner, &useGraph](Operation *walkedOp) {
2718 SymbolOpInterface op = llvm::dyn_cast<SymbolOpInterface>(walkedOp);
2719 if (!op || !cleaner.getTryToEraseSet().contains(op)) {
2720 return;
2721 }
2722 // If needed, rebuild use graph to reflect deletions.
2723 if (!useGraph) {
2724 useGraph = &getAnalysis<SymbolUseGraph>();
2725 }
2726 // If the op has any users, report the warning.
2727 if (useGraph->lookupNode(op)->hasPredecessor()) {
2728 op.emitWarning("Parameterized definition still has uses!").report();
2729 }
2730 });
2731 } else {
2732 LLVM_DEBUG(llvm::dbgs() << "[Cleanup(preimage)] failed\n";);
2733 }
2734 return res;
2735 }
2736
2737 LogicalResult eraseUnreachableFromConcreteDefinitions(ModuleOp rootMod) {
2738 SmallVector<SymbolOpInterface> roots;
2739 rootMod.walk([&roots](Operation *op) {
2740 if (isErasableDefinition(op) && !Step6_Cleanup::FromKeepSet::hasTemplateSymbolBindings(op)) {
2741 roots.push_back(llvm::cast<SymbolOpInterface>(op));
2742 }
2743 });
2744
2745 Step6_Cleanup::FromKeepSet cleaner(
2746 rootMod, getAnalysis<SymbolDefTree>(), getAnalysis<SymbolUseGraph>()
2747 );
2748 return cleaner.eraseUnreachableFrom(roots);
2749 }
2750
2751 LogicalResult eraseUnreachableFromMainStruct(ModuleOp rootMod, bool emitWarning = true) {
2752 Step6_Cleanup::FromKeepSet cleaner(
2753 rootMod, getAnalysis<SymbolDefTree>(), getAnalysis<SymbolUseGraph>()
2754 );
2755 FailureOr<SymbolLookupResult<StructDefOp>> mainOpt =
2756 getMainInstanceDef(cleaner.tables, rootMod.getOperation());
2757 if (failed(mainOpt)) {
2758 return failure();
2759 }
2760 SymbolLookupResult<StructDefOp> main = mainOpt.value();
2761 if (emitWarning && !main) {
2762 // Emit warning if there is no main specified because all cleanup-candidate definitions not
2763 // reachable from global defs may be removed.
2764 rootMod.emitWarning()
2765 .append(
2766 "using option '", cleanupMode.getArgStr(), '=',
2767 stringifyFlatteningCleanupMode(FlatteningCleanupMode::MainAsRoot), "' with no \"",
2769 "\" attribute on the top-level module may remove all cleanup-candidate definitions!"
2770 )
2771 .report();
2772 }
2773 SmallVector<SymbolOpInterface> roots;
2774 if (main) {
2775 roots.push_back(*main);
2776 }
2777 return cleaner.eraseUnreachableFrom(roots);
2778 }
2779};
2780
2781} // namespace
#define DEBUG_TYPE
Common private implementation for poly dialect passes.
This file defines methods symbol lookup across LLZK operations and included files.
static std::string from(mlir::Type type)
Return a brief string representation of the given LLZK type.
Definition TypeHelper.h:55
::mlir::TypedValue<::llzk::array::ArrayType > getArrRef()
Gets the SSA Value for the referenced array.
inline ::llzk::array::ArrayType getArrRefType()
Gets the type of the referenced array.
ArrayType cloneWith(std::optional<::llvm::ArrayRef< int64_t > > shape, ::mlir::Type elementType) const
Clone this type with the given shape and element type.
::mlir::Type getElementType() const
static ArrayType get(::mlir::Type elementType, ::llvm::ArrayRef<::mlir::Attribute > dimensionSizes)
Definition Types.cpp.inc:83
::llvm::ArrayRef<::mlir::Attribute > getDimensionSizes() const
::mlir::TypedValue<::llzk::array::ArrayType > getResult()
Definition Ops.h.inc:408
::mlir::DenseI32ArrayAttr getNumDimsPerMapAttr()
Definition Ops.h.inc:421
static constexpr ::llvm::StringLiteral getOperationName()
Definition Ops.h.inc:377
::mlir::OperandRangeRange getMapOperands()
Definition Ops.h.inc:392
::mlir::TypedValue<::mlir::Type > getResult()
Definition Ops.h.inc:923
::mlir::TypedValue<::llzk::array::ArrayType > getArrRef()
Definition Ops.h.inc:899
::mlir::TypedValue<::llzk::array::ArrayType > getArrRef()
Definition Ops.h.inc:1070
::mlir::TypedValue<::mlir::Type > getRvalue()
Definition Ops.h.inc:1078
static constexpr ::llvm::StringLiteral getOperationName()
Definition Ops.h.inc:353
void setType(::mlir::Type attrValue)
Definition Ops.cpp.inc:556
::std::optional<::mlir::Attribute > getTableOffset()
Definition Ops.cpp.inc:979
void setTableOffsetAttr(::mlir::Attribute attr)
Definition Ops.h.inc:753
::mlir::Value getVal()
Gets the SSA Value that holds the read/write data for the MemberRefOp.
::mlir::FailureOr< SymbolLookupResult< MemberDefOp > > getMemberDefOp(::mlir::SymbolTableCollection &tables)
Gets the definition for the member referenced in this op.
Definition Ops.cpp:689
static constexpr ::llvm::StringLiteral getOperationName()
Definition Ops.h.inc:942
StructType getType(::std::optional<::mlir::ArrayAttr > constParams={})
Gets the StructType representing this struct.
static constexpr ::llvm::StringLiteral getOperationName()
Definition Ops.h.inc:1170
::llvm::StringRef getSymName()
Definition Ops.cpp.inc:1608
void setSymName(::llvm::StringRef attrValue)
Definition Ops.cpp.inc:1613
::mlir::SymbolRefAttr getNameRef() const
static StructType get(::mlir::SymbolRefAttr structName)
Definition Types.cpp.inc:79
::mlir::FailureOr< SymbolLookupResult< StructDefOp > > getDefinition(::mlir::SymbolTableCollection &symbolTable, ::mlir::Operation *op, bool reportMissing=true) const
Gets the struct op that defines this struct.
Definition Types.cpp:26
::mlir::ArrayAttr getParams() const
bool calleeIsStructConstrain()
Return true iff the callee function name is FUNC_NAME_CONSTRAIN within a StructDefOp.
Definition Ops.cpp:1187
::llzk::component::StructType getSingleResultTypeOfCompute()
Assuming the callee is FUNC_NAME_COMPUTE, return the single StructType result.
Definition Ops.cpp:1210
::mlir::SymbolRefAttr getCalleeAttr()
Definition Ops.h.inc:292
::llvm::LogicalResult verifySymbolUses(::mlir::SymbolTableCollection &symbolTable)
Definition Ops.cpp:1103
bool calleeIsStructCompute()
Return true iff the callee function name is FUNC_NAME_COMPUTE within a StructDefOp.
Definition Ops.cpp:1175
::mlir::SymbolRefAttr getCallee()
Definition Ops.cpp.inc:470
::mlir::FunctionType getTypeSignature()
Return the FunctionType inferred from the arg operands and result types of this CallOp.
Definition Ops.cpp:1143
void setTemplateParamsAttr(::mlir::ArrayAttr attr)
Definition Ops.h.inc:316
::mlir::Operation::operand_range getArgOperands()
Definition Ops.h.inc:266
::mlir::ArrayAttr getTemplateParamsAttr()
Definition Ops.h.inc:297
::mlir::OperandRangeRange getMapOperands()
Definition Ops.h.inc:270
void setCalleeAttr(::mlir::SymbolRefAttr attr)
Definition Ops.h.inc:312
::mlir::FailureOr< UnificationMap > unifyTypeSignature(::mlir::FunctionType other)
Attempt type unfication between the inferred FunctionType from this CallOp (as LHS) and the given Fun...
Definition Ops.cpp:1147
::mlir::LogicalResult verifyTemplateParamsMatchInferred(::llvm::iterator_range<::mlir::Region::op_iterator<::llzk::polymorphic::TemplateParamOp > > targetParamDefs, const UnificationMap &unifications)
Verify that each template parameter value provided in this CallOp is consistent with the value inferr...
Definition Ops.cpp:705
::mlir::LogicalResult verifyTemplateParamCompatibility(::mlir::Attribute paramFromCallOp, ::llzk::polymorphic::TemplateParamOp targetParam)
Check type compatibility of the given template parameter value from this CallOp against the declared ...
::mlir::FailureOr<::llzk::SymbolLookupResult<::llzk::function::FuncDefOp > > getCalleeTarget(::mlir::SymbolTableCollection &tables)
Resolve and return the target FuncDefOp for this CallOp.
Definition Ops.cpp:1203
::mlir::DenseI32ArrayAttr getNumDimsPerMapAttr()
Definition Ops.h.inc:302
FuncDefOp clone(::mlir::IRMapping &mapper)
Create a deep copy of this function and all of its blocks, remapping any operands that use values out...
::mlir::FunctionType getFunctionType()
Definition Ops.cpp.inc:984
::llvm::ArrayRef<::mlir::Type > getArgumentTypes()
Required by FunctionOpInterface.
Definition Ops.h.inc:870
::llzk::component::StructType getSingleResultTypeOfCompute()
Assuming the name is FUNC_NAME_COMPUTE, return the single StructType result.
Definition Ops.cpp:497
::llvm::StringRef getSymName()
Definition Ops.cpp.inc:979
bool isStructCompute()
Return true iff the function is within a StructDefOp and named FUNC_NAME_COMPUTE.
Definition Ops.h.inc:899
bool isInStruct()
Return true iff the function is within a StructDefOp.
Definition Ops.h.inc:896
void setFunctionType(::mlir::FunctionType attrValue)
Definition Ops.cpp.inc:1003
void setSymName(::llvm::StringRef attrValue)
Definition Ops.cpp.inc:999
::mlir::StringAttr getSymNameAttr()
Definition Ops.h.inc:711
::mlir::Operation::operand_range getOperands()
Definition Ops.h.inc:1011
::mlir::FlatSymbolRefAttr getConstNameAttr()
Definition Ops.h.inc:464
::mlir::StringAttr getSymNameAttr()
Definition Ops.h.inc:673
::mlir::Region & getInitializerRegion()
Definition Ops.h.inc:660
::llvm::StringRef getSymName()
Definition Ops.cpp.inc:838
::mlir::Region & getBodyRegion()
Definition Ops.h.inc:872
bool hasConstNamed(::mlir::StringRef find)
Return true if there is an op of type OpT with the given name within the body region.
Definition Ops.h.inc:949
::mlir::StringAttr getSymNameAttr()
Definition Ops.h.inc:885
void setSymName(::llvm::StringRef attrValue)
Definition Ops.cpp.inc:1064
::llvm::StringRef getSymName()
Definition Ops.cpp.inc:1059
inline ::llvm::iterator_range<::mlir::Region::op_iterator< OpT > > getConstOps()
Return ops of type OpT within the body region.
Definition Ops.h.inc:921
::mlir::StringAttr getSymNameAttr()
Definition Ops.h.inc:1173
::mlir::FlatSymbolRefAttr getNameRef() const
Shared state for post-instantiation cleanup helpers.
Definition SharedImpl.h:67
CleanupBase(mlir::ModuleOp root, const SymbolDefTree &symDefTree, const SymbolUseGraph &symUseGraph)
int main(int argc, char **argv)
std::string toStringList(InputIt begin, InputIt end)
Generate a comma-separated string representation by traversing elements from begin to end where the e...
Definition Debug.h:156
component::StructType getStructTypeWithParams(mlir::SymbolRefAttr nameRef, mlir::ArrayAttr params)
Build a struct type while representing an empty parameter list as absent.
Definition SharedImpl.h:121
mlir::ConversionTarget newConverterDefinedTargetWithCallback(mlir::TypeConverter &tyConv, mlir::MLIRContext *ctx, LegalityCheckCallback &cb, AdditionalChecks &&...checks)
Return a new ConversionTarget allowing all LLZK-required dialects and defining Op legality based on t...
Definition SharedImpl.h:217
InstantiationLayout buildInstantiationLayout(TemplateOp parentTemplate, mlir::ArrayAttr callParams, const llvm::DenseMap< mlir::Attribute, mlir::Attribute > &paramNameToConcrete)
Derive the instantiated template name and the remaining explicit parameters that should stay on the r...
Definition SharedImpl.h:145
mlir::ConversionTarget newConverterDefinedTarget(mlir::TypeConverter &tyConv, mlir::MLIRContext *ctx, AdditionalChecks &&...checks)
Return a new ConversionTarget allowing all LLZK-required dialects and defining Op legality based on t...
Definition SharedImpl.h:201
bool isErasableDefinition(mlir::Operation *op)
Return true iff op is a cleanup candidate.
std::unique_ptr<::mlir::Pass > createEmptyTemplateRemovalPass()
::llvm::StringRef stringifyFlatteningCleanupMode(FlatteningCleanupMode val)
OpClass replaceOpWithNewOp(Rewriter &rewriter, mlir::Operation *op, Args &&...args)
Wrapper for PatternRewriter::replaceOpWithNewOp() that automatically copies discardable attributes (i...
std::unique_ptr<::mlir::Pass > createRemoveUnusedDiscardableAllocationsPass()
bool typeListsUnify(Iter1 lhs, Iter2 rhs, mlir::ArrayRef< llvm::StringRef > rhsReversePrefix={}, UnificationMap *unifications=nullptr)
Return true iff the two lists of Type instances are equivalent or could be equivalent after full inst...
Definition TypeHelper.h:277
bool isConcreteType(Type type, bool allowStructParams)
mlir::FailureOr< SymbolLookupResultUntyped > lookupTopLevelSymbol(mlir::SymbolTableCollection &tables, mlir::SymbolRefAttr symbol, mlir::Operation *origin, bool reportMissing=true)
FailureOr< StructType > getMainInstanceType(Operation *lookupFrom)
std::optional< mlir::SymbolTable::UseRange > getSymbolUses(mlir::Operation *from)
Get an iterator range for all of the uses, for any symbol, that are nested within the given operation...
TypeClass getIfSingleton(mlir::TypeRange types)
Definition TypeHelper.h:307
AttrConcreteness
Concreteness classification for an argument to a parameterized struct type.
Definition TypeHelper.h:123
mlir::DenseMap< std::pair< mlir::SymbolRefAttr, Side >, mlir::Attribute > UnificationMap
Optional result from type unifications.
Definition TypeHelper.h:223
std::string stringWithoutType(mlir::Attribute a)
bool isNullOrEmpty(mlir::ArrayAttr a)
SymbolRefAttr appendLeaf(SymbolRefAttr orig, FlatSymbolRefAttr newLeaf)
OpClass getParentOfType(mlir::Operation *op)
Return the closest surrounding parent/ancestor operation that is of type 'OpClass'.
Definition OpHelpers.h:51
AttrConcreteness classifyAttrConcreteness(Attribute attr, bool allowStructParams)
ArrayType flattenArrayElementType(ArrayType outerArrTy, Type elementType)
TypeClass getAtIndex(mlir::TypeRange types, size_t index)
Definition TypeHelper.h:311
mlir::RewritePatternSet newGeneralRewritePatternSet(mlir::TypeConverter &tyConv, mlir::MLIRContext *ctx, mlir::ConversionTarget &target)
Return a new RewritePatternSet covering all LLZK op types that may contain a StructType.
mlir::SymbolRefAttr asSymbolRefAttr(mlir::StringAttr root, mlir::SymbolRefAttr tail)
Build a SymbolRefAttr that prepends tail with root, i.e., root::tail.
int64_t fromAPInt(const llvm::APInt &i)
FailureOr< SymbolLookupResult< StructDefOp > > getMainInstanceDef(SymbolTableCollection &symbolTable, Operation *lookupFrom)
bool isMoreConcreteUnification(Type oldTy, Type newTy, llvm::function_ref< bool(Type oldTy, Type newTy)> knownOldToNew)
llvm::SmallVector< FlatSymbolRefAttr > getPieces(SymbolRefAttr ref)
constexpr char MAIN_ATTR_NAME[]
Name of the attribute on the top-level ModuleOp that specifies the type of the main struct.
Definition Constants.h:37
Groups the information needed after concrete parameters have been chosen to decide how to name a new ...
Definition SharedImpl.h:136
mlir::SmallVector< mlir::Attribute > remainingNames
Definition SharedImpl.h:137