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 matchAndRewrite(UnifiableCastOp op, PatternRewriter &rewriter) const override {
485 if (op.getInput().getType() != op.getResult().getType()) {
486 return failure();
487 }
488 rewriter.replaceOp(op, op.getInput());
489 return success();
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
538static std::string
539buildWildcardSpecializationName(StringRef baseName, const WildcardArraySpecializationInfo &info) {
540 return buildOpaqueInstantiationName(baseName, info.getConcreteTypeAttrs());
541}
542
545class CallStructFuncPattern : public OpConversionPattern<CallOp> {
546public:
547 CallStructFuncPattern(TypeConverter &converter, MLIRContext *ctx)
548 : OpConversionPattern<CallOp>(converter, ctx, /*benefit=*/1) {}
549
550 LogicalResult matchAndRewrite(
551 CallOp op, OpAdaptor adapter, ConversionPatternRewriter &rewriter
552 ) const override {
553 SmallVector<Type> newResultTypes;
554 if (failed(getTypeConverter()->convertTypes(op.getResultTypes(), newResultTypes))) {
555 return op->emitError("Could not convert Op result types.");
556 }
557
558 SymbolRefAttr calleeAttr = op.getCalleeAttr();
559 if (op.calleeIsStructCompute()) {
560 if (StructType newStTy = getIfSingleton<StructType>(newResultTypes)) {
561 calleeAttr = appendLeaf(newStTy.getNameRef(), calleeAttr.getLeafReference());
562 }
563 } else if (op.calleeIsStructConstrain()) {
564 if (StructType newStTy = getAtIndex<StructType>(adapter.getArgOperands().getTypes(), 0)) {
565 calleeAttr = appendLeaf(newStTy.getNameRef(), calleeAttr.getLeafReference());
566 }
567 }
568
570 rewriter, op, newResultTypes, calleeAttr, adapter.getMapOperands(),
571 op.getNumDimsPerMapAttr(), adapter.getArgOperands()
572 );
573 return success();
574 }
575};
576
579class MemberDefOpPattern : public OpConversionPattern<MemberDefOp> {
580public:
581 MemberDefOpPattern(TypeConverter &converter, MLIRContext *ctx)
582 : OpConversionPattern<MemberDefOp>(converter, ctx, /*benefit=*/1) {}
583
584 LogicalResult
585 matchAndRewrite(MemberDefOp op, OpAdaptor, ConversionPatternRewriter &rewriter) const override {
586 Type oldMemberType = op.getType();
587 Type newMemberType = getTypeConverter()->convertType(oldMemberType);
588 if (oldMemberType == newMemberType) {
589 return failure();
590 }
591 rewriter.modifyOpInPlace(op, [&op, &newMemberType]() { op.setType(newMemberType); });
592 return success();
593 }
594};
595
596static LogicalResult verifyNestedCallSymbols(FuncDefOp func) {
597 SymbolTableCollection tables;
598 auto result = func.walk([&tables](CallOp nestedCall) -> WalkResult {
599 return nestedCall.verifySymbolUses(tables);
600 });
601 return failure(result.wasInterrupted());
602}
603
604static LogicalResult applyWildcardSpecializationConversions(
605 FuncDefOp newFunc, const WildcardArraySpecializationInfo &info
606) {
607 MLIRContext *ctx = newFunc.getContext();
608 WildcardArrayTypeConverter tyConv(info.replacements);
609 ConversionTarget target = newConverterDefinedTarget<>(tyConv, ctx);
610 RewritePatternSet patterns = newGeneralRewritePatternSet(tyConv, ctx, target);
611 if (failed(applyFullConversion(newFunc, target, std::move(patterns)))) {
612 return failure();
613 }
614 return verifyNestedCallSymbols(newFunc);
615}
616
617static LogicalResult applyWildcardSpecializationConversions(
618 FuncDefOp newFunc, FunctionType newFuncTy, const WildcardArraySpecializationInfo &info
619) {
620 updateFuncSignature(newFunc, newFuncTy);
621 if (!info.hasConflictingReplacements) {
622 return applyWildcardSpecializationConversions(newFunc, info);
623 }
624 return verifyNestedCallSymbols(newFunc);
625}
626
627static LogicalResult applyWildcardSpecializationConversions(
628 StructDefOp newStruct, StructType oldStructType, StructType newStructType,
629 const WildcardArraySpecializationInfo &info
630) {
631 MLIRContext *ctx = newStruct.getContext();
632 WildcardArrayTypeConverter tyConv(info.replacements, oldStructType, newStructType);
633 ConversionTarget target = newConverterDefinedTarget<>(tyConv, ctx);
634 RewritePatternSet patterns = newGeneralRewritePatternSet(tyConv, ctx, target);
635 patterns.add<CallStructFuncPattern, MemberDefOpPattern>(tyConv, ctx);
636 return applyFullConversion(newStruct, target, std::move(patterns));
637}
638
639static FailureOr<SymbolRefAttr> getOrCreateSpecializedFreeFunc(
640 CallOp op, PatternRewriter &rewriter, SymbolTableCollection &symTables, FuncDefOp targetFunc,
641 const WildcardArraySpecializationInfo &info, FunctionType callSig
642) {
643 ModuleOp parentModule = getParentOfType<ModuleOp>(targetFunc);
644 assert(parentModule && "free function must be nested in a module");
645
646 std::string newFuncName = buildWildcardSpecializationName(targetFunc.getSymName(), info);
647 FuncDefOp newFunc;
648 if (Operation *existing = symTables.getSymbolTable(parentModule).lookup(newFuncName)) {
649 newFunc = llvm::dyn_cast<FuncDefOp>(existing);
650 if (!newFunc) {
651 return rewriter.notifyMatchFailure(op, [&](Diagnostic &diag) {
652 diag.append("specialized function name collision for '", newFuncName, '\'');
653 });
654 }
655 } else {
656 newFunc = targetFunc.clone();
657 newFunc.setSymName(newFuncName);
658 symTables.getSymbolTable(parentModule).insert(newFunc, Block::iterator(targetFunc));
659 if (failed(applyWildcardSpecializationConversions(newFunc, callSig, info))) {
660 newFunc.erase();
661 return rewriter.notifyMatchFailure(op, [&](Diagnostic &diag) {
662 diag.append("failure while creating wildcard-specialized function '", newFuncName, '\'');
663 });
664 }
665 }
666
667 return replaceLeafReference(op.getCalleeAttr(), newFunc.getSymName());
668}
669
670static FailureOr<StructType> getOrCreateSpecializedStruct(
671 CallOp op, PatternRewriter &rewriter, SymbolTableCollection &symTables,
672 StructDefOp targetStruct, const WildcardArraySpecializationInfo &info,
673 ConversionTracker &tracker
674) {
675 ModuleOp parentModule = getParentOfType<ModuleOp>(targetStruct);
676 assert(parentModule && "struct definition must be nested in a module");
677
678 StructType oldStructType = targetStruct.getType();
679 std::string newStructName = buildWildcardSpecializationName(targetStruct.getSymName(), info);
680 StructDefOp newStruct;
681 if (Operation *existing = symTables.getSymbolTable(parentModule).lookup(newStructName)) {
682 newStruct = llvm::dyn_cast<StructDefOp>(existing);
683 if (!newStruct) {
684 return rewriter.notifyMatchFailure(op, [&](Diagnostic &diag) {
685 diag.append("specialized struct name collision for '", newStructName, '\'');
686 });
687 }
688 } else {
689 newStruct = targetStruct.clone();
690 newStruct.setSymName(newStructName);
691 symTables.getSymbolTable(parentModule).insert(newStruct, Block::iterator(targetStruct));
692 StructType newStructType = newStruct.getType();
693 if (failed(
694 applyWildcardSpecializationConversions(newStruct, oldStructType, newStructType, info)
695 )) {
696 newStruct.erase();
697 return rewriter.notifyMatchFailure(op, [&](Diagnostic &diag) {
698 diag.append("failure while creating wildcard-specialized struct '", newStructName, '\'');
699 });
700 }
701 }
702
703 StructType newStructType = newStruct.getType();
704 tracker.recordSpecialization(oldStructType, newStructType);
705 return newStructType;
706}
707
710class SpecializeWildcardCallOp final : public OpRewritePattern<CallOp> {
711 ConversionTracker &tracker_;
712
713public:
714 SpecializeWildcardCallOp(MLIRContext *ctx, ConversionTracker &tracker)
715 : OpRewritePattern<CallOp>(ctx), tracker_(tracker) {}
716
717 LogicalResult matchAndRewrite(CallOp op, PatternRewriter &rewriter) const override {
718 if (calleeReferencesTemplateParam(op)) {
719 return failure();
720 }
721
722 SymbolTableCollection symTables;
723 FailureOr<SymbolLookupResult<FuncDefOp>> targetRes = op.getCalleeTarget(symTables);
724 if (failed(targetRes)) {
725 return failure();
726 }
727 FuncDefOp targetFunc = targetRes->get();
728 if (llvm::isa<TemplateOp>(targetFunc->getParentOp())) {
729 return failure();
730 }
731
732 FunctionType targetSig = targetFunc.getFunctionType();
733 FunctionType callSig = op.getTypeSignature();
734 StructDefOp targetStruct =
735 targetFunc.isInStruct() ? getParentOfType<StructDefOp>(targetFunc) : StructDefOp();
736 std::optional<StructType> ignoredStructType =
737 targetStruct ? std::optional<StructType>(targetStruct.getType()) : std::nullopt;
738 if (!containsWildcardArrayDims(targetSig) ||
739 !functionTypeIsMoreConcrete(
740 targetSig, callSig, tracker_, "SpecializeWildcardCallOp", ignoredStructType
741 )) {
742 return failure();
743 }
744
745 WildcardArraySpecializationInfo info;
746 if (failed(collectWildcardArraySpecializations(targetSig, callSig, info, ignoredStructType)) ||
747 info.empty()) {
748 return failure();
749 }
750
751 if (!targetFunc.isInStruct()) {
752 FailureOr<SymbolRefAttr> newCalleeAttr =
753 getOrCreateSpecializedFreeFunc(op, rewriter, symTables, targetFunc, info, callSig);
754 if (failed(newCalleeAttr)) {
755 return failure();
756 }
757 SmallVector<Type> newResultTypes;
758 FailureOr<SymbolLookupResult<FuncDefOp>> specializedFuncRes =
759 lookupTopLevelSymbol<FuncDefOp>(symTables, *newCalleeAttr, op);
760 if (failed(specializedFuncRes)) {
761 return failure();
762 }
763 newResultTypes.append(
764 specializedFuncRes->get().getFunctionType().getResults().begin(),
765 specializedFuncRes->get().getFunctionType().getResults().end()
766 );
767 if (!tracker_.areLegalConversions(
768 op.getResultTypes(), newResultTypes, "SpecializeWildcardCallOp"
769 )) {
770 return failure();
771 }
772 tracker_.recordInstantiation(op.getCalleeAttr());
773 tracker_.updateModifiedFlag(true);
775 rewriter, op, TypeRange(newResultTypes), *newCalleeAttr,
777 op.getArgOperands()
778 );
779 return success();
780 }
781
782 assert(targetStruct && "struct function must have a parent struct");
783 if (llvm::isa<TemplateOp>(targetStruct->getParentOp())) {
784 return failure();
785 }
786
787 StructType targetStructType = targetStruct.getType();
788 SmallVector<Type> newResultTypes;
789 if (targetFunc.nameIsConstrain()) {
790 StructType selfType = getAtIndex<StructType>(op.getArgOperands().getTypes(), 0);
791 if (!selfType) {
792 return failure();
793 }
794
795 std::string newStructName = buildWildcardSpecializationName(targetStruct.getSymName(), info);
796 SymbolRefAttr expectedSelfNameRef =
797 replaceLeafReference(targetStructType.getNameRef(), newStructName);
798 if (selfType.getNameRef() != expectedSelfNameRef) {
799 return failure();
800 }
801 }
802
803 FailureOr<StructType> newStructTypeRes =
804 getOrCreateSpecializedStruct(op, rewriter, symTables, targetStruct, info, tracker_);
805 if (failed(newStructTypeRes)) {
806 return failure();
807 }
808 StructType newStructType = *newStructTypeRes;
809
810 if (!targetFunc.nameIsConstrain()) {
811 WildcardArrayTypeConverter tyConv(info.replacements, targetStructType, newStructType);
812 if (failed(tyConv.convertTypes(op.getResultTypes(), newResultTypes))) {
813 return failure();
814 }
815 if (!tracker_.areLegalConversions(
816 op.getResultTypes(), newResultTypes, "SpecializeWildcardCallOp"
817 )) {
818 return failure();
819 }
820 }
821
822 tracker_.updateModifiedFlag(true);
823 SymbolRefAttr newCalleeAttr =
824 appendLeaf(newStructType.getNameRef(), op.getCallee().getLeafReference());
826 rewriter, op, TypeRange(newResultTypes), newCalleeAttr,
828 op.getArgOperands()
829 );
830 return success();
831 }
832};
833
836class RetargetStructConstrainCall final : public OpRewritePattern<CallOp> {
837 ConversionTracker &tracker_;
838
839public:
840 RetargetStructConstrainCall(MLIRContext *ctx, ConversionTracker &tracker)
841 : OpRewritePattern<CallOp>(ctx), tracker_(tracker) {}
842
843 LogicalResult matchAndRewrite(CallOp op, PatternRewriter &rewriter) const override {
844 if (!op.calleeIsStructConstrain() || op.getArgOperands().empty()) {
845 return failure();
846 }
847
848 StructType selfType = llvm::dyn_cast<StructType>(op.getArgOperands().front().getType());
849 if (!selfType) {
850 return failure();
851 }
852
853 SymbolTableCollection symTables;
854 FailureOr<SymbolLookupResult<FuncDefOp>> targetRes = op.getCalleeTarget(symTables);
855 if (failed(targetRes)) {
856 return failure();
857 }
858 FuncDefOp targetFunc = targetRes->get();
859 StructDefOp targetStruct = getParentOfType<StructDefOp>(targetFunc);
860 if (!targetStruct || selfType == targetStruct.getType()) {
861 return failure();
862 }
863
864 SymbolRefAttr newCalleeAttr =
865 appendLeaf(selfType.getNameRef(), op.getCallee().getLeafReference());
866 FailureOr<SymbolLookupResult<FuncDefOp>> specializedRes =
867 lookupTopLevelSymbol<FuncDefOp>(symTables, newCalleeAttr, op);
868 if (failed(specializedRes)) {
869 return failure();
870 }
871
872 tracker_.updateModifiedFlag(true);
874 rewriter, op, TypeRange(op.getResultTypes()), newCalleeAttr,
876 op.getArgOperands()
877 );
878 return success();
879 }
880};
881
882LogicalResult run(ModuleOp modOp, ConversionTracker &tracker) {
883 MLIRContext *ctx = modOp.getContext();
884 RewritePatternSet patterns(ctx);
885 patterns.add<RetargetStructConstrainCall>(ctx, tracker);
886 patterns.add<SpecializeWildcardCallOp>(ctx, tracker);
887 MatchFailureListener failureListener;
888 walkAndApplyPatterns(modOp, std::move(patterns), &failureListener);
889 return failure(failureListener.hadFailure);
890}
891
892} // namespace WildcardFunctionSpecialization
893
896class PassImpl : public llzk::polymorphic::impl::WildcardArraySpecializationPassBase<PassImpl> {
897public:
898 using Base = WildcardArraySpecializationPassBase<PassImpl>;
899 using Base::Base;
900
901private:
902 void runOnOperation() override {
903 ModuleOp modOp = getOperation();
904 ConversionTracker tracker;
905 unsigned loopCount = 0;
906 do {
907 ++loopCount;
908 if (loopCount > iterationLimit) {
909 llvm::errs() << DEBUG_TYPE << " exceeded the limit of " << iterationLimit
910 << " iterations!\n";
911 signalPassFailure();
912 return;
913 }
914 tracker.resetModifiedFlag();
915
916 if (failed(CastRefinement::run(modOp, tracker))) {
917 llvm::errs() << DEBUG_TYPE << " failed while refining wildcard array cast results\n";
918 signalPassFailure();
919 return;
920 }
921 if (failed(WildcardFunctionSpecialization::run(modOp, tracker))) {
922 llvm::errs() << DEBUG_TYPE
923 << " failed while specializing wildcard-array function signatures\n";
924 signalPassFailure();
925 return;
926 }
927 } while (tracker.isModified());
928
929 if (failed(erasePreimageOfInstantiations(
930 modOp, tracker, getAnalysis<SymbolDefTree>(), getAnalysis<SymbolUseGraph>()
931 ))) {
932 signalPassFailure();
933 }
934 }
935};
936
937} // 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.
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:1195
::mlir::SymbolRefAttr getCalleeAttr()
Definition Ops.h.inc:292
::llvm::LogicalResult verifySymbolUses(::mlir::SymbolTableCollection &symbolTable)
Definition Ops.cpp:1111
bool calleeIsStructCompute()
Return true iff the callee function name is FUNC_NAME_COMPUTE within a StructDefOp.
Definition Ops.cpp:1183
::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:1151
::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:1236
::mlir::FailureOr<::llzk::SymbolLookupResult<::llzk::function::FuncDefOp > > getCalleeTarget(::mlir::SymbolTableCollection &tables)
Resolve and return the target FuncDefOp for this CallOp.
Definition Ops.cpp:1211
::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:902
bool isInStruct()
Return true iff the function is within a StructDefOp.
Definition Ops.h.inc:909
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:951
::mlir::TypedValue<::mlir::Type > getInput()
Definition Ops.h.inc:1329
::mlir::TypedValue<::mlir::Type > getResult()
Definition Ops.h.inc:1348
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:183
std::string buildOpaqueInstantiationName(StringRef baseName, ArrayRef< Attribute > concreteAttrs)
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:302
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:53
TypeClass getAtIndex(mlir::TypeRange types, size_t index)
Definition TypeHelper.h:306
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)