LLZK 3.0.0
An open-source IR for Zero Knowledge (ZK) circuits
Loading...
Searching...
No Matches
TemplateInstantiation.cpp
Go to the documentation of this file.
1//===-- TemplateInstantiation.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 Veridise Inc.
6// SPDX-License-Identifier: Apache-2.0
7//
8//===----------------------------------------------------------------------===//
14//===----------------------------------------------------------------------===//
15
17
18#include "SharedImpl.h"
19
26#include "llzk/Util/Debug.h"
29
30#include <mlir/Dialect/Arith/IR/Arith.h>
31#include <mlir/IR/AttrTypeSubElements.h>
32
33#include <llvm/ADT/TypeSwitch.h>
34
35#undef DEBUG_TYPE
36#define DEBUG_TYPE "llzk-flatten"
37
38using namespace mlir;
39using namespace llzk;
40using namespace llzk::array;
41using namespace llzk::component;
42using namespace llzk::constrain;
43using namespace llzk::felt;
44using namespace llzk::function;
45using namespace llzk::polymorphic;
46using namespace llzk::polymorphic::detail;
47
50static pod::PodType convertPodType(pod::PodType type, const TypeConverter &converter) {
51 SmallVector<pod::RecordAttr> records;
52 for (auto record : type.getRecords()) {
53 Type converted = converter.convertType(record.getType());
54 if (!converted) {
55 return {};
56 }
57 records.push_back(pod::RecordAttr::get(type.getContext(), record.getName(), converted));
58 }
59 return pod::PodType::get(type.getContext(), records);
60}
61
62namespace {
63
64template <typename Impl, typename Op, typename... HandledAttrs>
65class SymbolUserHelper : public OpConversionPattern<Op> {
66private:
67 const DenseMap<Attribute, Attribute> &paramNameToValue;
68
69 SymbolUserHelper(
70 TypeConverter &converter, MLIRContext *ctx, unsigned patternBenefit,
71 const DenseMap<Attribute, Attribute> &paramNameToInstantiatedValue
72 )
73 : OpConversionPattern<Op>(converter, ctx, patternBenefit),
74 paramNameToValue(paramNameToInstantiatedValue) {}
75
76public:
77 using OpAdaptor = typename mlir::OpConversionPattern<Op>::OpAdaptor;
78
79 virtual Attribute getNameAttr(Op) const = 0;
80
81 virtual LogicalResult handleDefaultRewrite(
82 Attribute, Op op, OpAdaptor, ConversionPatternRewriter &, Attribute a
83 ) const {
84 return op->emitOpError().append("expected value with type ", op.getType(), " but found ", a);
85 }
86
87 LogicalResult
88 matchAndRewrite(Op op, OpAdaptor adaptor, ConversionPatternRewriter &rewriter) const override {
89 LLVM_DEBUG(llvm::dbgs() << "[SymbolUserHelper] op: " << op << '\n');
90 auto res = this->paramNameToValue.find(getNameAttr(op));
91 if (res == this->paramNameToValue.end()) {
92 LLVM_DEBUG(llvm::dbgs() << "[SymbolUserHelper] no instantiation for " << op << '\n');
93 return failure();
94 }
95 llvm::TypeSwitch<Attribute, LogicalResult> TS(res->second);
96 llvm::TypeSwitch<Attribute, LogicalResult> *ptr = &TS;
97
98 ((ptr = &(ptr->template Case<HandledAttrs>([&](HandledAttrs a) {
99 return static_cast<const Impl *>(this)->handleRewrite(res->first, op, adaptor, rewriter, a);
100 }))),
101 ...);
102
103 return TS.Default([&](Attribute a) {
104 return handleDefaultRewrite(res->first, op, adaptor, rewriter, a);
105 });
106 }
107 friend Impl;
108};
109
110class ClonedBodyConstReadOpPattern
111 : public SymbolUserHelper<
112 ClonedBodyConstReadOpPattern, ConstReadOp, IntegerAttr, FeltConstAttr> {
113 SmallVector<Diagnostic> &diagnostics;
114
115 using super =
116 SymbolUserHelper<ClonedBodyConstReadOpPattern, ConstReadOp, IntegerAttr, FeltConstAttr>;
117
118public:
119 ClonedBodyConstReadOpPattern(
120 TypeConverter &converter, MLIRContext *ctx,
121 const DenseMap<Attribute, Attribute> &paramNameToInstantiatedValue,
122 SmallVector<Diagnostic> &instantiationDiagnostics
123 )
124 // benefit>0 so this applies instead of GeneralTypeReplacePattern<ConstReadOp>
125 : super(converter, ctx, /*patternBenefit=*/1, paramNameToInstantiatedValue),
126 diagnostics(instantiationDiagnostics) {}
127
128 Attribute getNameAttr(ConstReadOp op) const override { return op.getConstNameAttr(); }
129
130 LogicalResult handleRewrite(
131 Attribute sym, ConstReadOp op, OpAdaptor, ConversionPatternRewriter &rewriter, IntegerAttr a
132 ) const {
133 const APInt &attrValue = a.getValue();
134 Type origResTy = op.getType();
135 Type newResTy = getTypeConverter()->convertType(origResTy);
136 if (!newResTy) {
137 return op->emitOpError().append("could not convert result type ", origResTy);
138 }
139
140 if (FeltType ty = llvm::dyn_cast<FeltType>(newResTy)) {
142 rewriter, op, FeltConstAttr::get(getContext(), integerAttrToDynamicAPInt(a), ty)
143 );
144 return success();
145 }
146
147 if (llvm::isa<IndexType>(newResTy)) {
149 return success();
150 }
151
152 if (newResTy.isSignlessInteger(1)) {
153 // Treat 0 as false and any other value as true (but give a warning if it's not 1)
154 if (attrValue.isZero()) {
155 replaceOpWithNewOp<arith::ConstantIntOp>(rewriter, op, newResTy, false);
156 return success();
157 }
158 if (!attrValue.isOne()) {
159 Location opLoc = op.getLoc();
160 Diagnostic diag(opLoc, DiagnosticSeverity::Warning);
161 diag << "Interpreting non-zero value " << stringWithoutType(a) << " as true";
162 if (getContext()->shouldPrintOpOnDiagnostic()) {
163 diag.attachNote(opLoc) << "see current operation: " << *op;
164 }
165 diag.attachNote(UnknownLoc::get(getContext()))
166 << "when instantiating '" << StructDefOp::getOperationName() << "' parameter \"" << sym
167 << "\" for this specialization";
168 diagnostics.push_back(std::move(diag));
169 }
170 replaceOpWithNewOp<arith::ConstantIntOp>(rewriter, op, newResTy, true);
171 return success();
172 }
173 return op->emitOpError().append("unexpected result type ", newResTy);
174 }
175
176 LogicalResult handleRewrite(
177 Attribute, ConstReadOp op, OpAdaptor, ConversionPatternRewriter &rewriter, FeltConstAttr a
178 ) const {
179 replaceOpWithNewOp<FeltConstantOp>(rewriter, op, a);
180 return success();
181 }
182};
183
184static inline bool tableOffsetIsntSymbol(MemberReadOp op) {
185 return !llvm::isa_and_present<SymbolRefAttr>(op.getTableOffset().value_or(nullptr));
186}
187
190class ClonedMemberReadOpPattern
191 : public SymbolUserHelper<ClonedMemberReadOpPattern, MemberReadOp, IntegerAttr> {
192 using super = SymbolUserHelper<ClonedMemberReadOpPattern, MemberReadOp, IntegerAttr>;
193
194public:
195 ClonedMemberReadOpPattern(
196 TypeConverter &converter, MLIRContext *ctx,
197 const DenseMap<Attribute, Attribute> &paramNameToInstantiatedValue
198 )
199 // benefit>0 so this applies instead of GeneralTypeReplacePattern<MemberReadOp>
200 : super(converter, ctx, /*patternBenefit=*/1, paramNameToInstantiatedValue) {}
201
202 Attribute getNameAttr(MemberReadOp op) const override {
203 return op.getTableOffset().value_or(nullptr);
204 }
205
206 LogicalResult handleRewrite(
207 Attribute, MemberReadOp op, OpAdaptor, ConversionPatternRewriter &rewriter, IntegerAttr a
208 ) const {
209 rewriter.modifyOpInPlace(op, [&]() {
210 op.setTableOffsetAttr(rewriter.getIndexAttr(fromAPInt(a.getValue())));
211 });
212
213 return success();
214 }
215
216 LogicalResult handleDefaultRewrite(
217 Attribute, MemberReadOp op, OpAdaptor, ConversionPatternRewriter &, Attribute a
218 ) const override {
219 return op->emitOpError().append(
220 "table offset requires an integer template value, but found ", a
221 );
222 }
223
224 LogicalResult matchAndRewrite(
225 MemberReadOp op, OpAdaptor adaptor, ConversionPatternRewriter &rewriter
226 ) const override {
227 LLVM_DEBUG(llvm::dbgs() << "[ClonedMemberReadOpPattern] MemberReadOp: " << op << '\n';);
228 if (tableOffsetIsntSymbol(op)) {
229 return failure();
230 }
231
232 return super::matchAndRewrite(op, adaptor, rewriter);
233 }
234};
235
236class MappedTypeConverter : public TypeConverter {
237 StructType origTy;
238 StructType newTy;
239 const DenseMap<Attribute, Attribute> &paramNameToValue;
240
241 inline Attribute convertIfPossible(Attribute a) const {
242 auto res = this->paramNameToValue.find(a);
243 return (res != this->paramNameToValue.end()) ? res->second : a;
244 }
245
246public:
247 MappedTypeConverter(
248 StructType originalType, StructType newType,
250 const DenseMap<Attribute, Attribute> &paramNameToInstantiatedValue
251 )
252 : TypeConverter(), origTy(originalType), newTy(newType),
253 paramNameToValue(paramNameToInstantiatedValue) {
254
255 addConversion([](Type inputTy) { return inputTy; });
256
257 addConversion([this](pod::PodType type) { return convertPodType(type, *this); });
258
259 addConversion([this](StructType inputTy) {
260 LLVM_DEBUG(llvm::dbgs() << "[MappedTypeConverter] convert " << inputTy << '\n');
261
262 // Check for replacement of the full type
263 if (inputTy == this->origTy) {
264 return this->newTy;
265 }
266 // Check for replacement of parameter symbol names with concrete values
267 if (ArrayAttr inputTyParams = inputTy.getParams()) {
268 SmallVector<Attribute> updated;
269 for (Attribute a : inputTyParams) {
270 if (TypeAttr ta = dyn_cast<TypeAttr>(a)) {
271 updated.push_back(TypeAttr::get(this->convertType(ta.getValue())));
272 } else {
273 updated.push_back(convertIfPossible(a));
274 }
275 }
276 return getStructTypeWithParams(inputTy.getNameRef(), inputTy.getContext(), updated);
277 }
278 // Otherwise, return the type unchanged
279 return inputTy;
280 });
281
282 addConversion([this](ArrayType inputTy) {
283 // Check for replacement of parameter symbol names with concrete values
284 ArrayRef<Attribute> dimSizes = inputTy.getDimensionSizes();
285 if (!dimSizes.empty()) {
286 SmallVector<Attribute> updated;
287 for (Attribute a : dimSizes) {
288 updated.push_back(convertIfPossible(a));
289 }
290 // Array-valued type arguments add dimensions instead of nesting array elements.
292 inputTy.cloneWith(inputTy.getElementType(), updated),
293 this->convertType(inputTy.getElementType())
294 );
295 }
296 // Otherwise, return the type unchanged
297 return inputTy;
298 });
299
300 addConversion([this](TypeVarType inputTy) -> Type {
301 // Check for replacement of parameter symbol name with a concrete type
302 if (TypeAttr tyAttr = llvm::dyn_cast<TypeAttr>(convertIfPossible(inputTy.getNameRef()))) {
303 Type convertedType = tyAttr.getValue();
304 // Use the new type unless it contains a TypeVarType because a TypeVarType from a
305 // different struct references a parameter name from that other struct, not from the
306 // current struct so the reference would be invalid.
307 if (isConcreteType(convertedType)) {
308 return convertedType;
309 }
310 }
311 return inputTy;
312 });
313 }
314};
315
318class ClonedBodyCreateArrayOpPattern final : public OpConversionPattern<CreateArrayOp> {
319public:
320 using OpConversionPattern<CreateArrayOp>::OpConversionPattern;
321
322 LogicalResult matchAndRewrite(
323 CreateArrayOp op, OpAdaptor adaptor, ConversionPatternRewriter &rewriter
324 ) const override {
325 if (op.getElements().empty()) {
326 return failure();
327 }
328 auto newElemTy = dyn_cast_if_present<ArrayType>(
329 getTypeConverter()->convertType(op.getType().getElementType())
330 );
331 auto newResultTy =
332 dyn_cast_if_present<ArrayType>(getTypeConverter()->convertType(op.getType()));
333 if (!newElemTy || !newResultTy) {
334 return failure();
335 }
336 if (!op.getType().hasStaticShape()) {
337 return rewriter.notifyMatchFailure(op, "initialized array requires a static outer shape");
338 }
339 for (Value element : adaptor.getElements()) {
340 if (element.getType() != newElemTy) {
341 return rewriter.notifyMatchFailure(op, "converted initializer has unexpected type");
342 }
343 }
344
345 Location loc = op.getLoc();
346 auto allocation =
347 preserveDiscardableAttrs(op, CreateArrayOp::create(rewriter, loc, newResultTy));
348 ArrayIndexGen idxGen = ArrayIndexGen::from(op.getType());
349 for (auto [index, element] : llvm::enumerate(adaptor.getElements())) {
350 auto indices = idxGen.delinearize(checkedCast<int64_t>(index), loc, rewriter);
351 assert(indices && "static array initializer index should delinearize");
352 InsertArrayOp::create(rewriter, loc, allocation.getResult(), ValueRange(*indices), element);
353 }
354 rewriter.replaceOp(op, allocation.getResult());
355 return success();
356 }
357};
358
361class ClonedBodyArrayReadOpPattern final : public OpConversionPattern<ReadArrayOp> {
362public:
363 using OpConversionPattern<ReadArrayOp>::OpConversionPattern;
364
365 LogicalResult matchAndRewrite(
366 ReadArrayOp op, OpAdaptor adaptor, ConversionPatternRewriter &rewriter
367 ) const override {
368 Type newResultTy = getTypeConverter()->convertType(op.getResult().getType());
369 if (!llvm::isa<ArrayType>(newResultTy)) {
370 return failure();
371 }
373 rewriter, op, newResultTy, adaptor.getArrRef(), adaptor.getIndices()
374 );
375 return success();
376 }
377};
378
381class ClonedBodyArrayWriteOpPattern final : public OpConversionPattern<WriteArrayOp> {
382public:
383 using OpConversionPattern<WriteArrayOp>::OpConversionPattern;
384
385 LogicalResult matchAndRewrite(
386 WriteArrayOp op, OpAdaptor adaptor, ConversionPatternRewriter &rewriter
387 ) const override {
388 if (!llvm::isa<ArrayType>(adaptor.getRvalue().getType())) {
389 return failure();
390 }
392 rewriter, op, adaptor.getArrRef(), adaptor.getIndices(), adaptor.getRvalue()
393 );
394 return success();
395 }
396};
397
398static SymbolRefAttr
399convertCalleeSymRefs(SymbolRefAttr callee, const DenseMap<Attribute, Attribute> &paramNameToValue) {
400 auto it = paramNameToValue.find(FlatSymbolRefAttr::get(callee.getRootReference()));
401 if (it == paramNameToValue.end()) {
402 return callee;
403 }
404
405 auto tyAttr = llvm::dyn_cast<TypeAttr>(it->second);
406 if (!tyAttr) {
407 return callee;
408 }
409
410 auto structTy = llvm::dyn_cast<StructType>(tyAttr.getValue());
411 if (!structTy) {
412 return callee;
413 }
414
415 SmallVector<FlatSymbolRefAttr> newPieces = getPieces(structTy.getNameRef());
416 llvm::append_range(newPieces, callee.getNestedReferences());
417 return asSymbolRefAttr(newPieces);
418}
419
424static std::optional<Attribute>
425evaluateExpr(TemplateExprOp exprOp, const DenseMap<Attribute, Attribute> &paramNameToConcrete) {
426 // Map from SSA value in the expr body to its concrete Attribute.
427 DenseMap<Value, Attribute> valueMap;
428 for (Operation &bodyOp : exprOp.getInitializerRegion().front()) {
429 if (auto yieldOp = llvm::dyn_cast<YieldOp>(bodyOp)) {
430 auto it = valueMap.find(yieldOp.getVal());
431 return it != valueMap.end() ? std::make_optional(it->second) : std::nullopt;
432 }
433
434 if (auto constReadOp = llvm::dyn_cast<ConstReadOp>(bodyOp)) {
435 auto it = paramNameToConcrete.find(constReadOp.getConstNameAttr());
436 if (it == paramNameToConcrete.end()) {
437 return std::nullopt; // a referenced param is not concrete
438 }
439 // If the attribute type is `FeltType` but it's stored as an IntegerAttr, promote to
440 // a `FeltConstAttr`.
441 Attribute val = it->second;
442 if (auto intAttr = llvm::dyn_cast<IntegerAttr>(val)) {
443 if (auto feltTy = llvm::dyn_cast<FeltType>(constReadOp.getResult().getType())) {
444 val = FeltConstAttr::get(bodyOp.getContext(), integerAttrToDynamicAPInt(intAttr), feltTy);
445 }
446 }
447 valueMap[constReadOp.getResult()] = val;
448 continue;
449 }
450
451 // Gather constant attributes for all operands.
452 SmallVector<Attribute> operandAttrs;
453 operandAttrs.reserve(bodyOp.getNumOperands());
454 for (Value operand : bodyOp.getOperands()) {
455 auto it = valueMap.find(operand);
456 if (it == valueMap.end()) {
457 return std::nullopt; // operand not known as a constant
458 }
459 operandAttrs.push_back(it->second);
460 }
461
462 // Try constant folding.
463 SmallVector<OpFoldResult> foldResults;
464 if (succeeded(bodyOp.fold(operandAttrs, foldResults)) &&
465 foldResults.size() == bodyOp.getNumResults()) {
466 for (auto [result, fr] : llvm::zip_equal(bodyOp.getResults(), foldResults)) {
467 if (Attribute a = llvm::dyn_cast<Attribute>(fr)) {
468 valueMap[result] = a;
469 } else {
470 return std::nullopt;
471 }
472 }
473 }
474 }
475 return std::nullopt; // no YieldOp found (shouldn't happen in a valid expr)
476}
477
478} // namespace
479
481
482void reportDelayedDiagnostics(Operation *site, SmallVector<Diagnostic> &&diagnostics) {
483 DiagnosticEngine &engine = site->getContext()->getDiagEngine();
484 for (Diagnostic &diagnostic : diagnostics) {
485 for (Diagnostic &note : diagnostic.getNotes()) {
486 assert(note.getNotes().empty() && "notes cannot have notes attached");
487 if (isa<UnknownLoc>(note.getLocation())) {
488 note = std::move(Diagnostic(site->getLoc(), note.getSeverity()).append(note.str()));
489 }
490 }
491 engine.emit(std::move(diagnostic));
492 }
493}
494
495FailureOr<ArrayAttr> rebaseTemplateParams(
496 SymbolTableCollection &tables, ArrayAttr templateParams, Operation *lookupFrom,
497 ModuleOp destinationRoot, Operation *requestSite
498) {
499 if (!templateParams) {
500 return ArrayAttr::get(requestSite->getContext(), {});
501 }
502 AttrTypeReplacer rebaser;
503 rebaser.addReplacement(
504 [&tables, lookupFrom, destinationRoot,
505 requestSite](StructType type) -> std::optional<std::pair<Type, WalkResult>> {
506 auto found = type.getDefinition(tables, lookupFrom);
507 if (failed(found)) {
508 return std::make_pair(Type(type), WalkResult::interrupt());
509 }
510 if (found->viaInclude()) {
511 requestSite->emitError("inline includes before rebasing template parameters");
512 return std::make_pair(Type(type), WalkResult::interrupt());
513 }
514 auto name = getPathRelativeToAncestor(found->get(), destinationRoot, [requestSite, type] {
515 return requestSite->emitError("struct type argument ") << type;
516 });
517 if (failed(name)) {
518 return std::make_pair(Type(type), WalkResult::interrupt());
519 }
520 auto nested =
521 rebaseTemplateParams(tables, type.getParams(), lookupFrom, destinationRoot, requestSite);
522 if (failed(nested)) {
523 return std::make_pair(Type(type), WalkResult::interrupt());
524 }
525 auto rebased = getStructTypeWithParams(*name, type.getParams() ? *nested : ArrayAttr());
526 return std::make_pair(Type(rebased), WalkResult::skip());
527 }
528 );
529 auto result = dyn_cast_if_present<ArrayAttr>(rebaser.replace(templateParams));
530 if (!result) {
531 return failure();
532 }
533 return result;
534}
535
536Attribute TemplateTypeConverter::convertIfPossible(Attribute attr) const {
537 auto it = paramNameToValue.find(attr);
538 return it == paramNameToValue.end() ? attr : it->second;
539}
540
541TemplateTypeConverter::TemplateTypeConverter(DenseMap<Attribute, Attribute> bindings)
542 : paramNameToValue(std::move(bindings)) {
543 addConversion([](Type t) { return t; });
544
545 addConversion([this](pod::PodType type) { return convertPodType(type, *this); });
546
547 addConversion([this](TypeVarType inputTy) -> Type {
548 if (TypeAttr tyAttr = llvm::dyn_cast<TypeAttr>(convertIfPossible(inputTy.getNameRef()))) {
549 Type convertedType = tyAttr.getValue();
550 if (isConcreteType(convertedType)) {
551 return convertedType;
552 }
553 }
554 return inputTy;
555 });
556
557 addConversion([this](ArrayType inputTy) {
558 SmallVector<Attribute> updated;
559 bool changed = false;
560 for (Attribute a : inputTy.getDimensionSizes()) {
561 Attribute converted = convertIfPossible(a);
562 updated.push_back(converted);
563 if (converted != a) {
564 changed = true;
565 }
566 }
567 Type newElemTy = this->convertType(inputTy.getElementType());
568 if (!changed && newElemTy == inputTy.getElementType()) {
569 return inputTy;
570 }
571 return flattenArrayElementType(inputTy.cloneWith(inputTy.getElementType(), updated), newElemTy);
572 });
573
574 addConversion([this](StructType inputTy) -> StructType {
575 if (ArrayAttr params = inputTy.getParams()) {
576 SmallVector<Attribute> updated;
577 bool changed = false;
578 for (Attribute a : params) {
579 if (TypeAttr ta = dyn_cast<TypeAttr>(a)) {
580 Type newTy = this->convertType(ta.getValue());
581 if (newTy != ta.getValue()) {
582 updated.push_back(TypeAttr::get(newTy));
583 changed = true;
584 continue;
585 }
586 } else {
587 Attribute converted = convertIfPossible(a);
588 if (converted != a) {
589 updated.push_back(converted);
590 changed = true;
591 continue;
592 }
593 }
594 updated.push_back(a);
595 }
596 if (changed) {
597 return getStructTypeWithParams(inputTy.getNameRef(), inputTy.getContext(), updated);
598 }
599 }
600 return inputTy;
601 });
602}
603
604Attribute TemplateTypeConverter::convertAttr(Attribute attr) const {
605 if (TypeAttr tyAttr = llvm::dyn_cast<TypeAttr>(attr)) {
606 Type convertedTy = convertType(tyAttr.getValue());
607 if (convertedTy != tyAttr.getValue()) {
608 return TypeAttr::get(convertedTy);
609 }
610 }
611 return convertIfPossible(attr);
612}
613
614void convertCalleesInPlace(Operation *op, const DenseMap<Attribute, Attribute> &paramNameToValue) {
615 op->walk([&paramNameToValue](CallOp callOp) {
616 callOp.setCalleeAttr(convertCalleeSymRefs(callOp.getCalleeAttr(), paramNameToValue));
617 });
618}
619
624 TemplateOp templateOp, DenseMap<Attribute, Attribute> &paramNameToConcrete
625) {
626 LLVM_DEBUG(
627 llvm::dbgs() << "[evaluateTemplateExprs] before: " << debug::toStringList(paramNameToConcrete)
628 << '\n'
629 );
630 for (TemplateExprOp exprOp : templateOp.getConstOps<TemplateExprOp>()) {
631 std::optional<Attribute> result = evaluateExpr(exprOp, paramNameToConcrete);
632 if (result.has_value()) {
633 auto exprNameAttr = FlatSymbolRefAttr::get(exprOp.getSymNameAttr());
634 paramNameToConcrete.try_emplace(exprNameAttr, *result);
635 LLVM_DEBUG(
636 llvm::dbgs() << "[evaluateTemplateExprs] expr @" << exprOp.getSymName()
637 << " evaluated to " << *result << '\n'
638 );
639 }
640 }
641 LLVM_DEBUG(
642 llvm::dbgs() << "[evaluateTemplateExprs] after: " << debug::toStringList(paramNameToConcrete)
643 << '\n'
644 );
645}
646
648std::optional<Attribute>
649inferUnifiedParam(const UnificationMap &unifyResult, SymbolRefAttr paramName) {
650 auto it = unifyResult.find({paramName, Side::RHS});
651 return (it == unifyResult.end()) ? std::nullopt : std::make_optional(it->second);
652}
653
655 StructDefOp newStruct, StructType typeAtDef,
656 const DenseMap<Attribute, Attribute> &paramNameToConcrete, SmallVector<Diagnostic> &diagnostics
657) {
658 MLIRContext *ctx = newStruct.getContext();
659 MappedTypeConverter tyConv(typeAtDef, newStruct.getType(), paramNameToConcrete);
660 ConversionTarget target =
661 newConverterDefinedTarget<EmitEqualityOp>(tyConv, ctx, tableOffsetIsntSymbol);
662 target.addDynamicallyLegalOp<ConstReadOp>([&paramNameToConcrete](ConstReadOp op) {
663 // Legal if it's not in the map of concrete attribute instantiations
664 return !paramNameToConcrete.contains(op.getConstNameAttr());
665 });
666
667 RewritePatternSet patterns = newGeneralRewritePatternSet<EmitEqualityOp>(tyConv, ctx, target);
668 patterns.add<ClonedBodyConstReadOpPattern>(tyConv, ctx, paramNameToConcrete, diagnostics);
669 patterns.add<ClonedBodyCreateArrayOpPattern>(tyConv, ctx);
670 patterns.add<ClonedBodyArrayReadOpPattern, ClonedBodyArrayWriteOpPattern>(tyConv, ctx);
671 patterns.add<ClonedMemberReadOpPattern>(tyConv, ctx, paramNameToConcrete);
672 return applyFullConversion(newStruct, target, std::move(patterns));
673}
674
676 FuncDefOp newFunc, const DenseMap<Attribute, Attribute> &paramNameToConcrete,
677 SmallVector<Diagnostic> &delayedDiagnostics
678) {
679 MLIRContext *ctx = newFunc.getContext();
680 TemplateTypeConverter tyConv(paramNameToConcrete);
681 ConversionTarget target = newConverterDefinedTarget<>(tyConv, ctx, tableOffsetIsntSymbol);
682 target.addDynamicallyLegalOp<ConstReadOp>([&tyConv](ConstReadOp p) {
683 // Legal if it's not in the map of concrete attribute instantiations
684 return !tyConv.containsParam(p.getConstNameAttr());
685 });
686 RewritePatternSet bodyPatterns = newGeneralRewritePatternSet(tyConv, ctx, target);
687 bodyPatterns.add<ClonedBodyConstReadOpPattern>(
688 tyConv, ctx, tyConv.getParamMap(), delayedDiagnostics
689 );
690 bodyPatterns.add<ClonedBodyArrayReadOpPattern, ClonedBodyArrayWriteOpPattern>(tyConv, ctx);
691 bodyPatterns.add<ClonedBodyCreateArrayOpPattern>(tyConv, ctx);
692 bodyPatterns.add<ClonedMemberReadOpPattern>(tyConv, ctx, paramNameToConcrete);
693 return applyFullConversion(newFunc, target, std::move(bodyPatterns));
694}
695
696} // namespace llzk::polymorphic::detail
Common private implementation for poly dialect passes.
Private parameter substitution helpers shared by polymorphic transformations.
static ArrayIndexGen from(ArrayType)
Construct new ArrayIndexGen. Will assert if hasStaticShape() is false.
std::optional< llvm::SmallVector< mlir::Value > > delinearize(int64_t, mlir::Location, mlir::OpBuilder &) const
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
static CreateArrayOp create(::mlir::OpBuilder &builder, ::mlir::Location location, ::llzk::array::ArrayType result, ::mlir::ValueRange elements={})
Definition Ops.cpp.inc:718
::mlir::Operation::operand_range getElements()
Definition Ops.h.inc:592
static InsertArrayOp create(::mlir::OpBuilder &builder, ::mlir::Location location, ::mlir::Value arr_ref, ::mlir::ValueRange indices, ::mlir::Value rvalue)
Definition Ops.cpp.inc:1442
::mlir::TypedValue<::mlir::Type > getResult()
Definition Ops.h.inc:1222
::std::optional<::mlir::Attribute > getTableOffset()
Definition Ops.cpp.inc:1132
void setTableOffsetAttr(::mlir::Attribute attr)
Definition Ops.h.inc:917
StructType getType(::std::optional<::mlir::ArrayAttr > constParams={})
Gets the StructType representing this struct.
static constexpr ::llvm::StringLiteral getOperationName()
Definition Ops.h.inc:1367
::mlir::SymbolRefAttr getNameRef() const
::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
::mlir::SymbolRefAttr getCalleeAttr()
Definition Ops.h.inc:456
void setCalleeAttr(::mlir::SymbolRefAttr attr)
Definition Ops.h.inc:486
static PodType get(::mlir::MLIRContext *context, ::llvm::ArrayRef<::llzk::pod::RecordAttr > records)
Definition Types.cpp.inc:70
::llvm::ArrayRef<::llzk::pod::RecordAttr > getRecords() const
::mlir::FlatSymbolRefAttr getConstNameAttr()
Definition Ops.h.inc:599
::mlir::StringAttr getSymNameAttr()
Definition Ops.h.inc:824
::mlir::Region & getInitializerRegion()
Definition Ops.h.inc:811
::llvm::StringRef getSymName()
Definition Ops.cpp.inc:962
inline ::llvm::iterator_range<::mlir::Region::op_iterator< OpT > > getConstOps()
Return ops of type OpT within the body region.
Definition Ops.h.inc:1102
::mlir::FlatSymbolRefAttr getNameRef() const
Substitute known template bindings in function types and inference candidates.
const llvm::DenseMap< mlir::Attribute, mlir::Attribute > & getParamMap() const
Return the bindings used by body substitution patterns.
bool containsParam(mlir::Attribute name) const
Return whether the parameter has a supplied binding.
mlir::Attribute convertAttr(mlir::Attribute attr) const
Substitute a bound attribute or the type contained in a TypeAttr.
TemplateTypeConverter(llvm::DenseMap< mlir::Attribute, mlir::Attribute > bindings)
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
Common interface for constant parameter-like symbol operations that can be defined within a TemplateO...
component::StructType getStructTypeWithParams(mlir::SymbolRefAttr nameRef, mlir::ArrayAttr params)
Build a struct type while representing an empty parameter list as absent.
Definition SharedImpl.h:117
FailureOr< ArrayAttr > rebaseTemplateParams(SymbolTableCollection &tables, ArrayAttr templateParams, Operation *lookupFrom, ModuleOp destinationRoot, Operation *requestSite)
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:178
std::optional< Attribute > inferUnifiedParam(const UnificationMap &unifyResult, SymbolRefAttr paramName)
Return the callee-side unification-derived value for a template parameter, if any.
LogicalResult substituteStructBody(StructDefOp newStruct, StructType typeAtDef, const DenseMap< Attribute, Attribute > &paramNameToConcrete, SmallVector< Diagnostic > &diagnostics)
LogicalResult substituteFunctionBody(FuncDefOp newFunc, const DenseMap< Attribute, Attribute > &paramNameToConcrete, SmallVector< Diagnostic > &delayedDiagnostics)
void evaluateTemplateExprs(TemplateOp templateOp, DenseMap< Attribute, Attribute > &paramNameToConcrete)
Evaluate all TemplateExprOps in templateOp that can be computed from the currently-known concrete par...
void convertCalleesInPlace(Operation *op, const DenseMap< Attribute, Attribute > &paramNameToValue)
void reportDelayedDiagnostics(Operation *site, SmallVector< Diagnostic > &&diagnostics)
OpClass replaceOpWithNewOp(Rewriter &rewriter, mlir::Operation *op, Args &&...args)
Wrapper for RewriterBase::replaceOpWithNewOp() that automatically copies discardable attributes (i....
bool isConcreteType(Type type, bool allowStructParams)
mlir::DenseMap< std::pair< mlir::SymbolRefAttr, Side >, mlir::Attribute > UnificationMap
Optional result from type unifications.
Definition TypeHelper.h:208
std::string stringWithoutType(mlir::Attribute a)
constexpr T checkedCast(U u) noexcept
Definition Compare.h:94
OpTy preserveDiscardableAttrs(mlir::Operation *src, OpTy dst)
Copy discardable attributes from src to dst.
ArrayType flattenArrayElementType(ArrayType outerArrTy, Type elementType)
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)
Extract a verified MLIR index value; malformed internal IR is a fatal error.
FailureOr< SymbolRefAttr > getPathRelativeToAncestor(SymbolOpInterface symbol, ModuleOp ancestor, llvm::function_ref< InFlightDiagnostic()> emitError)
llvm::SmallVector< FlatSymbolRefAttr > getPieces(SymbolRefAttr ref)
llvm::DynamicAPInt integerAttrToDynamicAPInt(mlir::IntegerAttr attr)
Decode an MLIR integer using its signedness; signless i1 denotes a boolean.