LLZK 3.0.0
An open-source IR for Zero Knowledge (ZK) circuits
Loading...
Searching...
No Matches
WildcardArraySpecializationPass.cpp
Go to the documentation of this file.
1//===-- WildcardArraySpecializationPass.cpp ---------------------*- C++ -*-===//
2//
3// Part of the LLZK Project, under the Apache License v2.0.
4// See LICENSE.txt for license information.
5// Copyright 2026 Project LLZK
6// SPDX-License-Identifier: Apache-2.0
7//
8//===----------------------------------------------------------------------===//
13//===----------------------------------------------------------------------===//
14
15#include "SharedImpl.h"
16
27#include "llzk/Util/Debug.h"
32
33#include <mlir/IR/BuiltinOps.h>
34#include <mlir/Pass/PassManager.h>
35#include <mlir/Transforms/DialectConversion.h>
36#include <mlir/Transforms/GreedyPatternRewriteDriver.h>
37#include <mlir/Transforms/WalkPatternRewriteDriver.h>
38
39// Include the generated base pass class definitions.
40namespace llzk::polymorphic {
41#define GEN_PASS_DEF_WILDCARDARRAYSPECIALIZATIONPASS
43} // namespace llzk::polymorphic
44
45#define DEBUG_TYPE "llzk-specialize-wildcard-arrays"
46
47using namespace mlir;
48using namespace llzk;
49using namespace llzk::array;
50using namespace llzk::component;
51using namespace llzk::function;
52using namespace llzk::polymorphic;
53using namespace llzk::polymorphic::detail;
54
55namespace {
56
59class ConversionTracker {
60 bool modified = false;
61 DenseMap<StructType, SmallVector<StructType>> structSpecializations;
62 DenseMap<StructType, StructType> reverseSpecializations;
63 DenseSet<SymbolRefAttr> funcInstantiations;
64
65public:
66 bool isModified() const { return modified; }
67 void resetModifiedFlag() { modified = false; }
68 void updateModifiedFlag(bool currStepModified) { modified |= currStepModified; }
69
70 void recordInstantiation(SymbolRefAttr funcName) {
71 funcInstantiations.insert(funcName);
72 modified = true;
73 }
74
75 void recordSpecialization(StructType oldType, StructType newType) {
76 assert(isNullOrEmpty(oldType.getParams()) && "wildcard-array specialization expects plain key");
77 SmallVector<StructType> &specializations = structSpecializations[oldType];
78 if (llvm::is_contained(specializations, newType)) {
79 assert(reverseSpecializations.lookup(newType) == oldType);
80 return;
81 }
82 specializations.push_back(newType);
83 auto [it, inserted] = reverseSpecializations.try_emplace(newType, oldType);
84 (void)it;
85 (void)inserted;
86 assert(inserted || it->second == oldType);
87 modified = true;
88 }
89
90 DenseSet<SymbolRefAttr> getInstantiatedDefinitionNames() const {
91 DenseSet<SymbolRefAttr> instantiatedNames = funcInstantiations;
92 for (const auto &[origRemoteTy, _] : structSpecializations) {
93 instantiatedNames.insert(origRemoteTy.getNameRef());
94 }
95 return instantiatedNames;
96 }
97
98 bool isLegalConversion(Type oldType, Type newType, const char *patName) const {
99 std::function<bool(Type, Type)> checkSpecializations = [&](Type oTy, Type nTy) {
100 if (StructType oldStructType = llvm::dyn_cast<StructType>(oTy)) {
101 auto specializationIt = structSpecializations.find(oldStructType);
102 if (specializationIt != structSpecializations.end() &&
103 llvm::is_contained(specializationIt->second, nTy)) {
104 return true;
105 }
106 }
107 if (StructType newStructType = llvm::dyn_cast<StructType>(nTy)) {
108 if (StructType preImage = reverseSpecializations.lookup(newStructType)) {
109 if (isMoreConcreteUnification(oTy, preImage, checkSpecializations)) {
110 return true;
111 }
112 }
113 }
114 return false;
115 };
116
117 if (!isMoreConcreteUnification(oldType, newType, checkSpecializations)) {
118 LLVM_DEBUG({
119 llvm::dbgs() << '[' << patName << "] invalid type conversion from " << oldType << " to "
120 << newType << '\n';
121 });
122 return false;
123 }
124 return true;
125 }
126
127 bool areLegalConversions(TypeRange oldTypes, TypeRange newTypes, const char *patName) const {
128 return oldTypes.size() == newTypes.size() &&
129 llvm::all_of(llvm::zip_equal(oldTypes, newTypes), [&](auto pair) {
130 return isLegalConversion(std::get<0>(pair), std::get<1>(pair), patName);
131 });
132 }
133};
134
136struct MatchFailureListener : public RewriterBase::Listener {
137 bool hadFailure = false;
138
139 void notifyMatchFailure(Location loc, function_ref<void(Diagnostic &)> reasonCallback) override {
140 InFlightDiagnostic diag = emitError(loc);
141 reasonCallback(*diag.getUnderlyingDiagnostic());
142 diag.report();
143 hadFailure = true;
144 }
145};
146
147static LogicalResult
148applyAndFoldGreedily(ModuleOp modOp, ConversionTracker &tracker, RewritePatternSet &&patterns) {
149 bool currStepModified = false;
150 MatchFailureListener failureListener;
151 LogicalResult result = applyPatternsGreedily(
152 modOp->getRegion(0), std::move(patterns),
153 GreedyRewriteConfig {.maxIterations = 20, .listener = &failureListener, .fold = true},
154 &currStepModified
155 );
156 tracker.updateModifiedFlag(currStepModified);
157 return failure(result.failed() || failureListener.hadFailure);
158}
159
161struct WildcardArraySpecializationInfo {
162 DenseMap<ArrayType, ArrayType> replacements;
163 SmallVector<std::pair<ArrayType, ArrayType>> ordered;
164 bool hasConflictingReplacements = false;
165
166 bool empty() const { return ordered.empty(); }
167
168 LogicalResult record(ArrayType oldTy, ArrayType newTy) {
169 ordered.emplace_back(oldTy, newTy);
170 auto it = replacements.find(oldTy);
171 if (it == replacements.end()) {
172 replacements.try_emplace(oldTy, newTy);
173 return success();
174 }
175 hasConflictingReplacements |= it->second != newTy;
176 return success();
177 }
178
179 SmallVector<Attribute> getConcreteTypeAttrs() const {
180 SmallVector<Attribute> attrs;
181 attrs.reserve(ordered.size());
182 for (const auto &[_, newTy] : ordered) {
183 attrs.push_back(TypeAttr::get(newTy));
184 }
185 return attrs;
186 }
187};
188
189static void updateFuncSignature(FuncDefOp func, FunctionType newFuncTy) {
190 FunctionType oldFuncTy = func.getFunctionType();
191 if (oldFuncTy == newFuncTy) {
192 return;
193 }
194
195 func.setFunctionType(newFuncTy);
196 Region &body = func.getFunctionBody();
197 if (body.empty()) {
198 return;
199 }
200
201 Block &entryBlock = body.front();
202 assert(entryBlock.getNumArguments() == newFuncTy.getNumInputs() && "function arity changed");
203 for (auto [arg, newTy] : llvm::zip_equal(entryBlock.getArguments(), newFuncTy.getInputs())) {
204 arg.setType(newTy);
205 }
206}
207
210static bool containsWildcardArrayDims(Type type) {
211 if (ArrayType arrTy = llvm::dyn_cast<ArrayType>(type)) {
212 if (llvm::any_of(arrTy.getDimensionSizes(), [](Attribute dim) {
213 if (IntegerAttr intAttr = llvm::dyn_cast<IntegerAttr>(dim)) {
214 return isDynamic(intAttr);
215 }
216 return false;
217 })) {
218 return true;
219 }
220 return containsWildcardArrayDims(arrTy.getElementType());
221 }
222 if (StructType structTy = llvm::dyn_cast<StructType>(type)) {
223 if (ArrayAttr params = structTy.getParams()) {
224 return llvm::any_of(params.getValue(), [](Attribute attr) {
225 if (TypeAttr typeAttr = llvm::dyn_cast<TypeAttr>(attr)) {
226 return containsWildcardArrayDims(typeAttr.getValue());
227 }
228 return false;
229 });
230 }
231 }
232 if (FunctionType funcTy = llvm::dyn_cast<FunctionType>(type)) {
233 return llvm::any_of(funcTy.getInputs(), containsWildcardArrayDims) ||
234 llvm::any_of(funcTy.getResults(), containsWildcardArrayDims);
235 }
236 return false;
237}
238
241static LogicalResult collectWildcardArraySpecializations(
242 Type oldTy, Type newTy, WildcardArraySpecializationInfo &out,
243 std::optional<StructType> ignoredStructType = std::nullopt
244) {
245 if (ignoredStructType.has_value() && oldTy == *ignoredStructType &&
246 llvm::isa<StructType>(newTy)) {
247 return success();
248 }
249 if (!typesUnify(oldTy, newTy)) {
250 return failure();
251 }
252 if (FunctionType oldFuncTy = llvm::dyn_cast<FunctionType>(oldTy)) {
253 FunctionType newFuncTy = llvm::dyn_cast<FunctionType>(newTy);
254 if (!newFuncTy || oldFuncTy.getNumInputs() != newFuncTy.getNumInputs() ||
255 oldFuncTy.getNumResults() != newFuncTy.getNumResults()) {
256 return failure();
257 }
258 for (auto [oldInput, newInput] :
259 llvm::zip_equal(oldFuncTy.getInputs(), newFuncTy.getInputs())) {
260 if (failed(collectWildcardArraySpecializations(oldInput, newInput, out, ignoredStructType))) {
261 return failure();
262 }
263 }
264 for (auto [oldResult, newResult] :
265 llvm::zip_equal(oldFuncTy.getResults(), newFuncTy.getResults())) {
266 if (failed(
267 collectWildcardArraySpecializations(oldResult, newResult, out, ignoredStructType)
268 )) {
269 return failure();
270 }
271 }
272 return success();
273 }
274 if (StructType oldStructTy = llvm::dyn_cast<StructType>(oldTy)) {
275 StructType newStructTy = llvm::dyn_cast<StructType>(newTy);
276 if (!newStructTy) {
277 return failure();
278 }
279 ArrayAttr oldParams = oldStructTy.getParams();
280 ArrayAttr newParams = newStructTy.getParams();
281 ArrayRef<Attribute> oldAttrs = oldParams ? oldParams.getValue() : ArrayRef<Attribute> {};
282 ArrayRef<Attribute> newAttrs = newParams ? newParams.getValue() : ArrayRef<Attribute> {};
283 if (oldAttrs.size() != newAttrs.size()) {
284 return failure();
285 }
286 for (auto [oldAttr, newAttr] : llvm::zip_equal(oldAttrs, newAttrs)) {
287 if (TypeAttr oldTypeAttr = llvm::dyn_cast<TypeAttr>(oldAttr)) {
288 TypeAttr newTypeAttr = llvm::dyn_cast<TypeAttr>(newAttr);
289 if (!newTypeAttr ||
290 failed(collectWildcardArraySpecializations(
291 oldTypeAttr.getValue(), newTypeAttr.getValue(), out, ignoredStructType
292 ))) {
293 return failure();
294 }
295 }
296 }
297 return success();
298 }
299 ArrayType oldArrTy = llvm::dyn_cast<ArrayType>(oldTy);
300 ArrayType newArrTy = llvm::dyn_cast<ArrayType>(newTy);
301 if (!oldArrTy || !newArrTy) {
302 return success();
303 }
304 if (oldArrTy.getDimensionSizes().size() != newArrTy.getDimensionSizes().size() ||
305 failed(collectWildcardArraySpecializations(
306 oldArrTy.getElementType(), newArrTy.getElementType(), out, ignoredStructType
307 ))) {
308 return failure();
309 }
310
311 bool changed = false;
312 for (auto [oldDim, newDim] :
313 llvm::zip_equal(oldArrTy.getDimensionSizes(), newArrTy.getDimensionSizes())) {
314 if (auto oldInt = llvm::dyn_cast<IntegerAttr>(oldDim); oldInt && isDynamic(oldInt)) {
315 if (auto newInt = llvm::dyn_cast<IntegerAttr>(newDim); newInt && !isDynamic(newInt)) {
316 changed = true;
317 }
318 }
319 }
320 if (!changed) {
321 return success();
322 }
323 return out.record(oldArrTy, newArrTy);
324}
325
326static bool functionTypeIsMoreConcrete(
327 FunctionType oldTy, FunctionType newTy, const ConversionTracker &tracker, const char *patName,
328 std::optional<StructType> ignoredStructType = std::nullopt
329) {
330 auto isCompatible = [&](Type oldType, Type newType) {
331 if (ignoredStructType.has_value() && oldType == *ignoredStructType &&
332 llvm::isa<StructType>(newType)) {
333 return true;
334 }
335 return tracker.isLegalConversion(oldType, newType, patName);
336 };
337
338 return oldTy.getNumInputs() == newTy.getNumInputs() &&
339 oldTy.getNumResults() == newTy.getNumResults() &&
340 llvm::all_of(llvm::zip_equal(oldTy.getInputs(), newTy.getInputs()), [&](auto pair) {
341 return isCompatible(std::get<0>(pair), std::get<1>(pair));
342 }) && llvm::all_of(llvm::zip_equal(oldTy.getResults(), newTy.getResults()), [&](auto pair) {
343 return isCompatible(std::get<0>(pair), std::get<1>(pair));
344 });
346
348/// concrete types inferred for a specialization.
349class WildcardArrayTypeConverter : public TypeConverter {
350 const DenseMap<ArrayType, ArrayType> &arrayReplacements_;
351 std::optional<StructType> oldStructType_;
352 std::optional<StructType> newStructType_;
354public:
355 WildcardArrayTypeConverter(
356 const DenseMap<ArrayType, ArrayType> &arrayReplacements,
357 std::optional<StructType> oldStructType = std::nullopt,
358 std::optional<StructType> newStructType = std::nullopt
360 : TypeConverter(), arrayReplacements_(arrayReplacements), oldStructType_(oldStructType),
361 newStructType_(newStructType) {
362 addConversion([](Type inputTy) { return inputTy; });
363
364 addConversion([this](ArrayType inputTy) -> Type {
365 Type newElemTy = this->convertType(inputTy.getElementType());
366 auto it = arrayReplacements_.find(inputTy);
367 if (it != arrayReplacements_.end()) {
368 ArrayType replacement = it->second;
369 if (replacement.getElementType() != newElemTy) {
370 return replacement.cloneWith(newElemTy);
371 }
372 return replacement;
373 }
374 if (newElemTy != inputTy.getElementType()) {
375 return inputTy.cloneWith(newElemTy);
376 }
377 return inputTy;
378 });
379
380 addConversion([this](StructType inputTy) -> Type {
381 if (oldStructType_.has_value() && newStructType_.has_value() && inputTy == *oldStructType_) {
382 return *newStructType_;
383 }
384 if (ArrayAttr params = inputTy.getParams()) {
385 SmallVector<Attribute> updated;
386 bool changed = false;
387 for (Attribute attr : params.getValue()) {
388 if (TypeAttr typeAttr = llvm::dyn_cast<TypeAttr>(attr)) {
389 Type newTy = this->convertType(typeAttr.getValue());
390 updated.push_back(TypeAttr::get(newTy));
391 changed |= newTy != typeAttr.getValue();
392 } else {
393 updated.push_back(attr);
394 }
395 }
396 if (changed) {
398 inputTy.getNameRef(), ArrayAttr::get(inputTy.getContext(), updated)
399 );
401 }
402 return inputTy;
403 });
405};
406
407
408
409static std::optional<Type> refineCastResultArrayWildcards(Type resultTy, Type inputTy) {
410 ArrayType resultArrTy = llvm::dyn_cast<ArrayType>(resultTy);
411 ArrayType inputArrTy = llvm::dyn_cast<ArrayType>(inputTy);
412 if (!resultArrTy || !inputArrTy) {
413 return std::nullopt;
415 if (resultArrTy.getDimensionSizes().size() != inputArrTy.getDimensionSizes().size() ||
416 !typesUnify(resultArrTy.getElementType(), inputArrTy.getElementType())) {
417 return std::nullopt;
418 }
419
420 SmallVector<Attribute> refinedDims;
421 bool changed = false;
422 for (auto [resultDim, inputDim] :
423 llvm::zip_equal(resultArrTy.getDimensionSizes(), inputArrTy.getDimensionSizes())) {
424 if (auto resultInt = llvm::dyn_cast<IntegerAttr>(resultDim);
425 resultInt && isDynamic(resultInt)) {
426 if (auto inputInt = llvm::dyn_cast<IntegerAttr>(inputDim); inputInt && !isDynamic(inputInt)) {
427 refinedDims.push_back(inputDim);
428 changed = true;
429 continue;
430 }
431 }
432 refinedDims.push_back(resultDim);
433 }
434
435 if (!changed) {
436 return std::nullopt;
437 }
438 return resultArrTy.cloneWith(resultArrTy.getElementType(), refinedDims);
439}
440
443static bool calleeReferencesTemplateParam(CallOp op) {
444 SymbolRefAttr callee = op.getCalleeAttr();
445 if (!callee || callee.getNestedReferences().size() != 1) {
446 return false;
447 }
448 TemplateOp parentTemplate = getParentOfType<TemplateOp>(op);
449 if (!parentTemplate) {
450 return false;
451 }
452 return parentTemplate.hasConstNamed<TemplateParamOp>(callee.getRootReference());
453}
454
455static LogicalResult erasePreimageOfInstantiations(
456 ModuleOp rootMod, const ConversionTracker &tracker, const SymbolDefTree &symDefTree,
457 const SymbolUseGraph &symUseGraph
458) {
459 FromEraseSet cleaner(rootMod, symDefTree, symUseGraph, tracker.getInstantiatedDefinitionNames());
460 LogicalResult res = cleaner.eraseUnusedDefinitions();
461 if (failed(res)) {
462 return res;
463 }
464 rootMod->walk([&cleaner, &symUseGraph](Operation *walkedOp) {
465 SymbolOpInterface op = llvm::dyn_cast<SymbolOpInterface>(walkedOp);
466 if (!op || !cleaner.getTryToEraseSet().contains(op)) {
467 return;
468 }
469 if (const SymbolUseGraphNode *node = symUseGraph.lookupNode(op);
470 node && node->hasPredecessor()) {
471 op.emitWarning("Parameterized definition still has uses!").report();
472 }
473 });
474 return success();
475}
476
477namespace CastRefinement {
478
480class RemoveIdentityUnifiableCast final : public OpRewritePattern<UnifiableCastOp> {
481public:
482 RemoveIdentityUnifiableCast(MLIRContext *ctx) : OpRewritePattern(ctx, 4) {}
483
484 LogicalResult match(UnifiableCastOp op) const override {
485 return success(op.getInput().getType() == op.getResult().getType());
486 }
487
488 void rewrite(UnifiableCastOp op, PatternRewriter &rewriter) const override {
489 rewriter.replaceOp(op, op.getInput());
490 }
491};
492
495class UpdateUnifiableCastResultType final : public OpRewritePattern<UnifiableCastOp> {
496 ConversionTracker &tracker_;
497
498public:
499 UpdateUnifiableCastResultType(MLIRContext *ctx, ConversionTracker &tracker)
500 : OpRewritePattern(ctx, 3), tracker_(tracker) {}
501
502 LogicalResult matchAndRewrite(UnifiableCastOp op, PatternRewriter &rewriter) const override {
503 std::optional<Type> refinedResultTy =
504 refineCastResultArrayWildcards(op.getResult().getType(), op.getInput().getType());
505 if (!refinedResultTy.has_value() || *refinedResultTy == op.getResult().getType()) {
506 return failure();
507 }
508 if (!tracker_.isLegalConversion(
509 op.getResult().getType(), *refinedResultTy, "UpdateUnifiableCastResultType"
510 )) {
511 return failure();
512 }
513 rewriter.modifyOpInPlace(op, [&]() { op.getResult().setType(*refinedResultTy); });
514 return success();
515 }
516};
517
518LogicalResult run(ModuleOp modOp, ConversionTracker &tracker) {
519 MLIRContext *ctx = modOp.getContext();
520 RewritePatternSet patterns(ctx);
521 patterns.add<RemoveIdentityUnifiableCast>(ctx);
522 patterns.add<UpdateUnifiableCastResultType>(ctx, tracker);
523 return applyAndFoldGreedily(modOp, tracker, std::move(patterns));
524}
525
526} // namespace CastRefinement
527
529
530static SymbolRefAttr replaceLeafReference(SymbolRefAttr symRef, StringRef newLeafName) {
531 SmallVector<FlatSymbolRefAttr> pieces = getPieces(symRef);
532 assert(!pieces.empty() && "symbol reference must have at least one piece");
533 pieces.back() = FlatSymbolRefAttr::get(StringAttr::get(symRef.getContext(), newLeafName));
534 return asSymbolRefAttr(pieces);
535}
536
537static std::string
538buildWildcardSpecializationName(StringRef baseName, const WildcardArraySpecializationInfo &info) {
539 return BuildShortTypeString::from(baseName.str(), info.getConcreteTypeAttrs());
540}
541
544class CallStructFuncPattern : public OpConversionPattern<CallOp> {
545public:
546 CallStructFuncPattern(TypeConverter &converter, MLIRContext *ctx)
547 : OpConversionPattern<CallOp>(converter, ctx, /*benefit=*/1) {}
548
549 LogicalResult matchAndRewrite(
550 CallOp op, OpAdaptor adapter, ConversionPatternRewriter &rewriter
551 ) const override {
552 SmallVector<Type> newResultTypes;
553 if (failed(getTypeConverter()->convertTypes(op.getResultTypes(), newResultTypes))) {
554 return op->emitError("Could not convert Op result types.");
555 }
556
557 SymbolRefAttr calleeAttr = op.getCalleeAttr();
558 if (op.calleeIsStructCompute()) {
559 if (StructType newStTy = getIfSingleton<StructType>(newResultTypes)) {
560 calleeAttr = appendLeaf(newStTy.getNameRef(), calleeAttr.getLeafReference());
561 }
562 } else if (op.calleeIsStructConstrain()) {
563 if (StructType newStTy = getAtIndex<StructType>(adapter.getArgOperands().getTypes(), 0)) {
564 calleeAttr = appendLeaf(newStTy.getNameRef(), calleeAttr.getLeafReference());
565 }
566 }
567
569 rewriter, op, newResultTypes, calleeAttr, adapter.getMapOperands(),
570 op.getNumDimsPerMapAttr(), adapter.getArgOperands()
571 );
572 return success();
573 }
574};
575
578class MemberDefOpPattern : public OpConversionPattern<MemberDefOp> {
579public:
580 MemberDefOpPattern(TypeConverter &converter, MLIRContext *ctx)
581 : OpConversionPattern<MemberDefOp>(converter, ctx, /*benefit=*/1) {}
582
583 LogicalResult
584 matchAndRewrite(MemberDefOp op, OpAdaptor, ConversionPatternRewriter &rewriter) const override {
585 Type oldMemberType = op.getType();
586 Type newMemberType = getTypeConverter()->convertType(oldMemberType);
587 if (oldMemberType == newMemberType) {
588 return failure();
589 }
590 rewriter.modifyOpInPlace(op, [&op, &newMemberType]() { op.setType(newMemberType); });
591 return success();
592 }
593};
594
595static LogicalResult verifyNestedCallSymbols(FuncDefOp func) {
596 SymbolTableCollection tables;
597 WalkResult result = func.walk([&tables](CallOp nestedCall) {
598 return WalkResult(nestedCall.verifySymbolUses(tables));
599 });
600 return failure(result.wasInterrupted());
601}
602
603static LogicalResult applyWildcardSpecializationConversions(
604 FuncDefOp newFunc, const WildcardArraySpecializationInfo &info
605) {
606 MLIRContext *ctx = newFunc.getContext();
607 WildcardArrayTypeConverter tyConv(info.replacements);
608 ConversionTarget target = newConverterDefinedTarget<>(tyConv, ctx);
609 RewritePatternSet patterns = newGeneralRewritePatternSet(tyConv, ctx, target);
610 if (failed(applyFullConversion(newFunc, target, std::move(patterns)))) {
611 return failure();
612 }
613 return verifyNestedCallSymbols(newFunc);
614}
615
616static LogicalResult applyWildcardSpecializationConversions(
617 FuncDefOp newFunc, FunctionType newFuncTy, const WildcardArraySpecializationInfo &info
618) {
619 updateFuncSignature(newFunc, newFuncTy);
620 if (!info.hasConflictingReplacements) {
621 return applyWildcardSpecializationConversions(newFunc, info);
622 }
623 return verifyNestedCallSymbols(newFunc);
624}
625
626static LogicalResult applyWildcardSpecializationConversions(
627 StructDefOp newStruct, StructType oldStructType, StructType newStructType,
628 const WildcardArraySpecializationInfo &info
629) {
630 MLIRContext *ctx = newStruct.getContext();
631 WildcardArrayTypeConverter tyConv(info.replacements, oldStructType, newStructType);
632 ConversionTarget target = newConverterDefinedTarget<>(tyConv, ctx);
633 RewritePatternSet patterns = newGeneralRewritePatternSet(tyConv, ctx, target);
634 patterns.add<CallStructFuncPattern, MemberDefOpPattern>(tyConv, ctx);
635 return applyFullConversion(newStruct, target, std::move(patterns));
636}
637
638static FailureOr<SymbolRefAttr> getOrCreateSpecializedFreeFunc(
639 CallOp op, PatternRewriter &rewriter, SymbolTableCollection &symTables, FuncDefOp targetFunc,
640 const WildcardArraySpecializationInfo &info, FunctionType callSig
641) {
642 ModuleOp parentModule = getParentOfType<ModuleOp>(targetFunc);
643 assert(parentModule && "free function must be nested in a module");
644
645 std::string newFuncName = buildWildcardSpecializationName(targetFunc.getSymName(), info);
646 FuncDefOp newFunc;
647 if (Operation *existing = symTables.getSymbolTable(parentModule).lookup(newFuncName)) {
648 newFunc = llvm::dyn_cast<FuncDefOp>(existing);
649 if (!newFunc) {
650 return rewriter.notifyMatchFailure(op, [&](Diagnostic &diag) {
651 diag.append("specialized function name collision for '", newFuncName, '\'');
652 });
653 }
654 } else {
655 newFunc = targetFunc.clone();
656 newFunc.setSymName(newFuncName);
657 symTables.getSymbolTable(parentModule).insert(newFunc, Block::iterator(targetFunc));
658 if (failed(applyWildcardSpecializationConversions(newFunc, callSig, info))) {
659 newFunc.erase();
660 return rewriter.notifyMatchFailure(op, [&](Diagnostic &diag) {
661 diag.append("failure while creating wildcard-specialized function '", newFuncName, '\'');
662 });
663 }
664 }
665
666 return replaceLeafReference(op.getCalleeAttr(), newFunc.getSymName());
667}
668
669static FailureOr<StructType> getOrCreateSpecializedStruct(
670 CallOp op, PatternRewriter &rewriter, SymbolTableCollection &symTables,
671 StructDefOp targetStruct, const WildcardArraySpecializationInfo &info,
672 ConversionTracker &tracker
673) {
674 ModuleOp parentModule = getParentOfType<ModuleOp>(targetStruct);
675 assert(parentModule && "struct definition must be nested in a module");
676
677 StructType oldStructType = targetStruct.getType();
678 std::string newStructName = buildWildcardSpecializationName(targetStruct.getSymName(), info);
679 StructDefOp newStruct;
680 if (Operation *existing = symTables.getSymbolTable(parentModule).lookup(newStructName)) {
681 newStruct = llvm::dyn_cast<StructDefOp>(existing);
682 if (!newStruct) {
683 return rewriter.notifyMatchFailure(op, [&](Diagnostic &diag) {
684 diag.append("specialized struct name collision for '", newStructName, '\'');
685 });
686 }
687 } else {
688 newStruct = targetStruct.clone();
689 newStruct.setSymName(newStructName);
690 symTables.getSymbolTable(parentModule).insert(newStruct, Block::iterator(targetStruct));
691 StructType newStructType = newStruct.getType();
692 if (failed(
693 applyWildcardSpecializationConversions(newStruct, oldStructType, newStructType, info)
694 )) {
695 newStruct.erase();
696 return rewriter.notifyMatchFailure(op, [&](Diagnostic &diag) {
697 diag.append("failure while creating wildcard-specialized struct '", newStructName, '\'');
698 });
699 }
700 }
701
702 StructType newStructType = newStruct.getType();
703 tracker.recordSpecialization(oldStructType, newStructType);
704 return newStructType;
705}
706
709class SpecializeWildcardCallOp final : public OpRewritePattern<CallOp> {
710 ConversionTracker &tracker_;
711
712public:
713 SpecializeWildcardCallOp(MLIRContext *ctx, ConversionTracker &tracker)
714 : OpRewritePattern<CallOp>(ctx), tracker_(tracker) {}
715
716 LogicalResult matchAndRewrite(CallOp op, PatternRewriter &rewriter) const override {
717 if (calleeReferencesTemplateParam(op)) {
718 return failure();
719 }
720
721 SymbolTableCollection symTables;
722 FailureOr<SymbolLookupResult<FuncDefOp>> targetRes = op.getCalleeTarget(symTables);
723 if (failed(targetRes)) {
724 return failure();
725 }
726 FuncDefOp targetFunc = targetRes->get();
727 if (llvm::isa<TemplateOp>(targetFunc->getParentOp())) {
728 return failure();
729 }
730
731 FunctionType targetSig = targetFunc.getFunctionType();
732 FunctionType callSig = op.getTypeSignature();
733 StructDefOp targetStruct =
734 targetFunc.isInStruct() ? getParentOfType<StructDefOp>(targetFunc) : StructDefOp();
735 std::optional<StructType> ignoredStructType =
736 targetStruct ? std::optional<StructType>(targetStruct.getType()) : std::nullopt;
737 if (!containsWildcardArrayDims(targetSig) ||
738 !functionTypeIsMoreConcrete(
739 targetSig, callSig, tracker_, "SpecializeWildcardCallOp", ignoredStructType
740 )) {
741 return failure();
742 }
743
744 WildcardArraySpecializationInfo info;
745 if (failed(collectWildcardArraySpecializations(targetSig, callSig, info, ignoredStructType)) ||
746 info.empty()) {
747 return failure();
748 }
749
750 if (!targetFunc.isInStruct()) {
751 FailureOr<SymbolRefAttr> newCalleeAttr =
752 getOrCreateSpecializedFreeFunc(op, rewriter, symTables, targetFunc, info, callSig);
753 if (failed(newCalleeAttr)) {
754 return failure();
755 }
756 SmallVector<Type> newResultTypes;
757 FailureOr<SymbolLookupResult<FuncDefOp>> specializedFuncRes =
758 lookupTopLevelSymbol<FuncDefOp>(symTables, *newCalleeAttr, op);
759 if (failed(specializedFuncRes)) {
760 return failure();
761 }
762 newResultTypes.append(
763 specializedFuncRes->get().getFunctionType().getResults().begin(),
764 specializedFuncRes->get().getFunctionType().getResults().end()
765 );
766 if (!tracker_.areLegalConversions(
767 op.getResultTypes(), newResultTypes, "SpecializeWildcardCallOp"
768 )) {
769 return failure();
770 }
771 tracker_.recordInstantiation(op.getCalleeAttr());
772 tracker_.updateModifiedFlag(true);
774 rewriter, op, TypeRange(newResultTypes), *newCalleeAttr,
776 op.getArgOperands()
777 );
778 return success();
779 }
780
781 assert(targetStruct && "struct function must have a parent struct");
782 if (llvm::isa<TemplateOp>(targetStruct->getParentOp())) {
783 return failure();
784 }
785
786 StructType targetStructType = targetStruct.getType();
787 SmallVector<Type> newResultTypes;
788 if (targetFunc.nameIsConstrain()) {
789 StructType selfType = getAtIndex<StructType>(op.getArgOperands().getTypes(), 0);
790 if (!selfType) {
791 return failure();
792 }
793
794 std::string newStructName = buildWildcardSpecializationName(targetStruct.getSymName(), info);
795 SymbolRefAttr expectedSelfNameRef =
796 replaceLeafReference(targetStructType.getNameRef(), newStructName);
797 if (selfType.getNameRef() != expectedSelfNameRef) {
798 return failure();
799 }
800 }
801
802 FailureOr<StructType> newStructTypeRes =
803 getOrCreateSpecializedStruct(op, rewriter, symTables, targetStruct, info, tracker_);
804 if (failed(newStructTypeRes)) {
805 return failure();
806 }
807 StructType newStructType = *newStructTypeRes;
808
809 if (!targetFunc.nameIsConstrain()) {
810 WildcardArrayTypeConverter tyConv(info.replacements, targetStructType, newStructType);
811 if (failed(tyConv.convertTypes(op.getResultTypes(), newResultTypes))) {
812 return failure();
813 }
814 if (!tracker_.areLegalConversions(
815 op.getResultTypes(), newResultTypes, "SpecializeWildcardCallOp"
816 )) {
817 return failure();
818 }
819 }
820
821 tracker_.updateModifiedFlag(true);
822 SymbolRefAttr newCalleeAttr =
823 appendLeaf(newStructType.getNameRef(), op.getCallee().getLeafReference());
825 rewriter, op, TypeRange(newResultTypes), newCalleeAttr,
827 op.getArgOperands()
828 );
829 return success();
830 }
831};
832
835class RetargetStructConstrainCall final : public OpRewritePattern<CallOp> {
836 ConversionTracker &tracker_;
837
838public:
839 RetargetStructConstrainCall(MLIRContext *ctx, ConversionTracker &tracker)
840 : OpRewritePattern<CallOp>(ctx), tracker_(tracker) {}
841
842 LogicalResult matchAndRewrite(CallOp op, PatternRewriter &rewriter) const override {
843 if (!op.calleeIsStructConstrain() || op.getArgOperands().empty()) {
844 return failure();
845 }
846
847 StructType selfType = llvm::dyn_cast<StructType>(op.getArgOperands().front().getType());
848 if (!selfType) {
849 return failure();
850 }
851
852 SymbolTableCollection symTables;
853 FailureOr<SymbolLookupResult<FuncDefOp>> targetRes = op.getCalleeTarget(symTables);
854 if (failed(targetRes)) {
855 return failure();
856 }
857 FuncDefOp targetFunc = targetRes->get();
858 StructDefOp targetStruct = getParentOfType<StructDefOp>(targetFunc);
859 if (!targetStruct || selfType == targetStruct.getType()) {
860 return failure();
861 }
862
863 SymbolRefAttr newCalleeAttr =
864 appendLeaf(selfType.getNameRef(), op.getCallee().getLeafReference());
865 FailureOr<SymbolLookupResult<FuncDefOp>> specializedRes =
866 lookupTopLevelSymbol<FuncDefOp>(symTables, newCalleeAttr, op);
867 if (failed(specializedRes)) {
868 return failure();
869 }
870
871 tracker_.updateModifiedFlag(true);
873 rewriter, op, TypeRange(op.getResultTypes()), newCalleeAttr,
875 op.getArgOperands()
876 );
877 return success();
878 }
879};
880
881LogicalResult run(ModuleOp modOp, ConversionTracker &tracker) {
882 MLIRContext *ctx = modOp.getContext();
883 RewritePatternSet patterns(ctx);
884 patterns.add<RetargetStructConstrainCall>(ctx, tracker);
885 patterns.add<SpecializeWildcardCallOp>(ctx, tracker);
886 MatchFailureListener failureListener;
887 walkAndApplyPatterns(modOp, std::move(patterns), &failureListener);
888 return failure(failureListener.hadFailure);
889}
890
891} // namespace WildcardFunctionSpecialization
892
895class PassImpl : public llzk::polymorphic::impl::WildcardArraySpecializationPassBase<PassImpl> {
896public:
897 using Base = WildcardArraySpecializationPassBase<PassImpl>;
898 using Base::Base;
899
900private:
901 void runOnOperation() override {
902 ModuleOp modOp = getOperation();
903 ConversionTracker tracker;
904 unsigned loopCount = 0;
905 do {
906 ++loopCount;
907 if (loopCount > iterationLimit) {
908 llvm::errs() << DEBUG_TYPE << " exceeded the limit of " << iterationLimit
909 << " iterations!\n";
910 signalPassFailure();
911 return;
912 }
913 tracker.resetModifiedFlag();
914
915 if (failed(CastRefinement::run(modOp, tracker))) {
916 llvm::errs() << DEBUG_TYPE << " failed while refining wildcard array cast results\n";
917 signalPassFailure();
918 return;
919 }
920 if (failed(WildcardFunctionSpecialization::run(modOp, tracker))) {
921 llvm::errs() << DEBUG_TYPE
922 << " failed while specializing wildcard-array function signatures\n";
923 signalPassFailure();
924 return;
925 }
926 } while (tracker.isModified());
927
928 if (failed(erasePreimageOfInstantiations(
929 modOp, tracker, getAnalysis<SymbolDefTree>(), getAnalysis<SymbolUseGraph>()
930 ))) {
931 signalPassFailure();
932 }
933 }
934};
935
936} // namespace
#define DEBUG_TYPE
Common private implementation for poly dialect passes.
This file defines methods symbol lookup across LLZK operations and included files.
Reusable MLIR dialect conversion functions for LLZK StructType replacement.
static std::string from(mlir::Type type)
Return a brief string representation of the given LLZK type.
Definition TypeHelper.h:55
Builds a tree structure representing the symbol table structure.
bool hasPredecessor() const
Return true if this node has any predecessors.
Builds a graph structure representing the relationships between symbols and their uses.
const SymbolUseGraphNode * lookupNode(mlir::ModuleOp pathRoot, mlir::SymbolRefAttr path) const
Return the existing node for the symbol reference relative to the given module, else nullptr.
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
::llvm::ArrayRef<::mlir::Attribute > getDimensionSizes() const
void setType(::mlir::Type attrValue)
Definition Ops.cpp.inc:556
StructType getType(::std::optional<::mlir::ArrayAttr > constParams={})
Gets the StructType representing this struct.
::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::ArrayAttr getParams() const
bool calleeIsStructConstrain()
Return true iff the callee function name is FUNC_NAME_CONSTRAIN within a StructDefOp.
Definition Ops.cpp:1187
::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
::mlir::Operation::operand_range getArgOperands()
Definition Ops.h.inc:266
::mlir::OperandRangeRange getMapOperands()
Definition Ops.h.inc:270
static ::llvm::SmallVector<::mlir::ValueRange > toVectorOfValueRange(::mlir::OperandRangeRange)
Allocate consecutive storage of the ValueRange instances in the parameter so it can be passed to the ...
Definition Ops.cpp:1228
::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::StringRef getSymName()
Definition Ops.cpp.inc:979
bool nameIsConstrain()
Return true iff the function name is FUNC_NAME_CONSTRAIN (if needed, a check that this FuncDefOp is l...
Definition Ops.h.inc:889
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
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::TypedValue<::mlir::Type > getInput()
Definition Ops.h.inc:1327
::mlir::TypedValue<::mlir::Type > getResult()
Definition Ops.h.inc:1346
Removes parameterized definitions whose instantiated replacements now cover every remaining use.
Definition SharedImpl.h:86
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
OpClass replaceOpWithNewOp(Rewriter &rewriter, mlir::Operation *op, Args &&...args)
Wrapper for PatternRewriter::replaceOpWithNewOp() that automatically copies discardable attributes (i...
mlir::FailureOr< SymbolLookupResultUntyped > lookupTopLevelSymbol(mlir::SymbolTableCollection &tables, mlir::SymbolRefAttr symbol, mlir::Operation *origin, bool reportMissing=true)
TypeClass getIfSingleton(mlir::TypeRange types)
Definition TypeHelper.h:307
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
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.
bool isDynamic(IntegerAttr intAttr)
mlir::SymbolRefAttr asSymbolRefAttr(mlir::StringAttr root, mlir::SymbolRefAttr tail)
Build a SymbolRefAttr that prepends tail with root, i.e., root::tail.
bool typesUnify(Type lhs, Type rhs, ArrayRef< StringRef > rhsReversePrefix, UnificationMap *unifications)
bool isMoreConcreteUnification(Type oldTy, Type newTy, llvm::function_ref< bool(Type oldTy, Type newTy)> knownOldToNew)
llvm::SmallVector< FlatSymbolRefAttr > getPieces(SymbolRefAttr ref)