LLZK 3.0.0
An open-source IR for Zero Knowledge (ZK) circuits
Loading...
Searching...
No Matches
StructSpecializationDiscovery.cpp
Go to the documentation of this file.
1//===-- StructSpecializationDiscovery.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//===----------------------------------------------------------------------===//
9
11
13#include "DiscoveryStorage.h"
14
23
24#include <mlir/Dialect/SCF/IR/SCF.h>
25#include <mlir/IR/AttrTypeSubElements.h>
26#include <mlir/IR/SymbolTable.h>
27#include <mlir/Interfaces/SideEffectInterfaces.h>
28
29#include <llvm/ADT/DenseSet.h>
30#include <llvm/ADT/TypeSwitch.h>
31
32#include <limits>
33#include <optional>
34#include <tuple>
35
36using namespace mlir;
37using namespace llzk;
38using namespace llzk::component;
39using namespace llzk::polymorphic;
40using namespace llzk::polymorphic::detail;
41
42namespace {
43
44using Environment = DenseMap<Value, DiscoveryValue>;
45
49class SpecializationTypeResolver {
51
52public:
55 explicit SpecializationTypeResolver(const StructSpecializationDiscovery::Bindings &parameters)
56 : bindings(parameters) {}
57
59 Attribute resolveAttribute(Attribute attr) const {
60 if (auto bound = bindings.lookup(attr)) {
61 attr = bound;
62 }
63 if (auto type = dyn_cast<TypeAttr>(attr)) {
64 return TypeAttr::get(resolveType(type.getValue()));
65 }
66 return attr;
67 }
68
70 Type resolveType(Type type) const {
71 AttrTypeReplacer replacer;
72 replacer.addReplacement([this](TypeVarType variable) -> std::optional<Type> {
73 if (auto bound = dyn_cast_or_null<TypeAttr>(bindings.lookup(variable.getNameRef()))) {
74 return bound.getValue();
75 }
76 return std::nullopt;
77 });
78 replacer.addReplacement(
79 [this](StructType structure) -> std::optional<std::pair<Type, WalkResult>> {
80 SmallVector<Attribute> args;
81 if (!structure.getParams()) {
82 return std::make_pair(Type(structure), WalkResult::skip());
83 }
84 for (Attribute arg : structure.getParams()) {
85 args.push_back(resolveAttribute(arg));
86 }
87 return std::make_pair(
88 Type(
89 StructType::get(structure.getNameRef(), ArrayAttr::get(structure.getContext(), args))
90 ),
91 WalkResult::skip()
92 );
93 }
94 );
95 replacer.addReplacement(
96 [this](array::ArrayType array) -> std::optional<std::pair<Type, WalkResult>> {
97 SmallVector<Attribute> dimensions;
98 for (Attribute dimension : array.getDimensionSizes()) {
99 dimensions.push_back(resolveAttribute(dimension));
100 }
101 return std::make_pair(
103 array.cloneWith(array.getElementType(), dimensions),
104 resolveType(array.getElementType())
105 )),
106 WalkResult::skip()
107 );
108 }
109 );
110 return replacer.replace(type);
111 }
112
114 Attribute resolveAffineArgument(AffineMapAttr attr, ArrayRef<Attribute> operands) const {
115 if (attr.getValue().getNumInputs() != operands.size() ||
116 llvm::any_of(operands, [](Attribute operand) {
117 return !llvm::isa_and_present<IntegerAttr>(operand);
118 })) {
119 return {};
120 }
121 SmallVector<Attribute> results;
122 bool poison = false;
123 if (failed(attr.getValue().constantFold(operands, results, &poison)) || poison ||
124 results.size() != 1) {
125 return {};
126 }
127 return results.front();
128 }
129
132 template <typename ResolveArgument>
133 FailureOr<StructType>
134 resolveAffineStructArguments(StructType structure, ResolveArgument &&resolveArgument) const {
135 SmallVector<Attribute> arguments;
136 for (Attribute argument : structure.getParams()) {
137 if (auto map = dyn_cast<AffineMapAttr>(argument)) {
138 argument = resolveArgument(map);
139 if (!argument) {
140 return failure();
141 }
142 }
143 arguments.push_back(argument);
144 }
145 return StructType::get(
146 structure.getNameRef(), ArrayAttr::get(structure.getContext(), arguments)
147 );
148 }
149};
150
163class DiscoveryRun {
164 Operation *source;
166 SpecializationTypeResolver typeResolver;
167 SymbolTableCollection tables;
168 DiscoveryStorage storage {source->getContext()};
170 DenseSet<std::tuple<Operation *, Type, ArrayAttr, Type, unsigned>> seen;
171 DenseSet<std::pair<Operation *, Type>> enumeratedArrays;
172 using FamilyKey = std::pair<Operation *, ArrayAttr>;
173 DenseMap<FamilyKey, SmallVector<StructType>> familyCandidates;
174 DenseSet<FamilyKey> knownFamilies;
175 SmallVector<std::pair<function::CallOp, StructType>> rolledCalls;
176 DenseSet<std::pair<Operation *, Type>> seenRolledCalls;
177 SmallVectorImpl<Operation *> *visitedOperations;
179
181 LogicalResult recordRequest(
182 Operation *site, StructType type, ArrayRef<int64_t> indices = {}, StructType familyType = {},
183 StructSpecializationRequest::Kind kind = StructSpecializationRequest::Kind::Plain
184 ) {
185 if (type.getParams()) {
186 for (Attribute argument : type.getParams()) {
187 if (auto nested = dyn_cast<TypeAttr>(argument)) {
188 if (hasAffineMapAttr(nested.getValue())) {
189 return site->emitError("affine maps nested in struct type arguments are not supported");
190 }
191 }
192 if (!isConcreteStructParamAttr(argument)) {
193 return site->emitError("cannot resolve struct specialization argument ") << argument;
194 }
195 if (auto nested = dyn_cast<TypeAttr>(argument)) {
196 if (failed(collectTypes(site, nested.getValue()))) {
197 return failure();
198 }
199 }
200 }
201 }
202 if (auto structure = dyn_cast<StructDefOp>(source);
203 structure &&
204 type == llvm::cast<StructType>(typeResolver.resolveType(structure.getType()))) {
205 return success();
206 }
207 SmallVector<Attribute> indexAttrs;
208 for (int64_t index : indices) {
209 indexAttrs.push_back(IntegerAttr::get(IndexType::get(context()), index));
210 }
211 auto requestKey = std::make_tuple(
212 site, Type(type), ArrayAttr::get(context(), indexAttrs), Type(familyType),
213 static_cast<unsigned>(kind)
214 );
215 if (!seen.insert(requestKey).second) {
216 return success();
217 }
218 requests.push_back({site, type, llvm::to_vector(indices), familyType, kind});
219 return success();
220 }
221
222 static bool hasDirectAffineArgument(StructType type) {
223 return type.getParams() && llvm::any_of(type.getParams(), [](Attribute argument) {
224 return isa<AffineMapAttr>(argument);
225 });
226 }
227
228 FailureOr<FamilyKey> getFamilyKey(Operation *site, StructType rolled) {
229 auto found = rolled.getDefinition(tables, site);
230 if (failed(found)) {
231 return failure();
232 }
233 return FamilyKey(found->get().getOperation(), rolled.getParams());
234 }
235
236 LogicalResult rememberFamily(Operation *site, StructType rolled) {
237 auto key = getFamilyKey(site, rolled);
238 if (failed(key)) {
239 return failure();
240 }
241 knownFamilies.insert(*key);
242 return success();
243 }
244
245 LogicalResult recordFamilyCandidate(Operation *site, StructType rolled, StructType concrete) {
246 auto key = getFamilyKey(site, rolled);
247 if (failed(key)) {
248 return failure();
249 }
250 knownFamilies.insert(*key);
251 auto &candidates = familyCandidates[*key];
252 if (!llvm::is_contained(candidates, concrete)) {
253 candidates.push_back(concrete);
254 }
255 return success();
256 }
257
260 LogicalResult
261 collectTypes(Operation *site, Type type, ArrayRef<int64_t> indices = {}, bool inArray = false) {
262 type = typeResolver.resolveType(type);
263 if (auto structure = dyn_cast<StructType>(type)) {
264 if (!hasDirectAffineArgument(structure)) {
265 return recordRequest(site, structure);
266 }
267 if (!inArray) {
268 if (isa<MemberDefOp>(site)) {
269 return site->emitError("affine struct type requires an enclosing array");
270 }
271 return success();
272 }
273 SmallVector<Attribute> operands;
274 for (int64_t index : indices) {
275 operands.push_back(IntegerAttr::get(IndexType::get(context()), index));
276 }
277 auto concrete = typeResolver.resolveAffineStructArguments(
278 structure, [this, site, &operands](AffineMapAttr map) -> Attribute {
279 if (map.getValue().getNumInputs() != operands.size()) {
280 site->emitError("affine struct argument requires one input per array dimension");
281 return {};
282 }
283 Attribute argument = typeResolver.resolveAffineArgument(map, operands);
284 if (!argument) {
285 site->emitError("cannot resolve affine array element specialization argument");
286 }
287 return argument;
288 }
289 );
290 if (failed(concrete)) {
291 return failure();
292 }
293 if (failed(recordFamilyCandidate(site, structure, *concrete))) {
294 return failure();
295 }
296 return recordRequest(
297 site, *concrete, indices, structure, StructSpecializationRequest::Kind::ArrayElement
298 );
299 }
300 if (auto array = dyn_cast<array::ArrayType>(type)) {
301 if (!hasAffineMapAttr(array.getElementType())) {
302 return collectTypes(site, array.getElementType());
303 }
304 if (!enumeratedArrays.insert({site, type}).second) {
305 return success();
306 }
307 SmallVector<int64_t> shape;
308 for (Attribute dimension : array.getDimensionSizes()) {
309 auto integer = dyn_cast<IntegerAttr>(dimension);
310 if (!integer || integer.getInt() < 0) {
311 return site->emitError("affine family discovery requires concrete array dimensions");
312 }
313 shape.push_back(integer.getInt());
314 }
315 if (llvm::is_contained(shape, 0)) {
316 bool invalid = false;
317 array.getElementType().walk([this, site, &invalid](Type nested) {
318 if (auto structure = dyn_cast<StructType>(nested);
319 structure && hasDirectAffineArgument(structure)) {
320 invalid |= failed(rememberFamily(site, structure));
321 }
322 });
323 return failure(invalid);
324 }
325 SmallVector<int64_t> elementIndices;
326 auto enumerate = [this, site, array, &shape,
327 &elementIndices](auto &&self, unsigned dimension) -> LogicalResult {
328 if (dimension < shape.size()) {
329 for (int64_t i = 0; i < shape[dimension]; ++i) {
330 elementIndices.push_back(i);
331 if (failed(self(self, dimension + 1))) {
332 return failure();
333 }
334 elementIndices.pop_back();
335 }
336 return success();
337 }
338 return collectTypes(site, array.getElementType(), elementIndices, true);
339 };
340 return enumerate(enumerate, 0);
341 }
342 if (auto record = dyn_cast<pod::PodType>(type)) {
343 for (auto field : record.getRecords()) {
344 if (failed(collectTypes(site, field.getType(), indices, inArray))) {
345 return failure();
346 }
347 }
348 return success();
349 }
350 return failure(type.walk([this, site, indices, inArray](StructType structure) -> WalkResult {
351 return collectTypes(site, structure, indices, inArray);
352 }).wasInterrupted());
353 }
354
356 LogicalResult discoverCallTypes(function::CallOp call, const Environment &env) {
357 unsigned group = 0;
358 for (Type result : call.getResultTypes()) {
359 auto structure = dyn_cast<StructType>(typeResolver.resolveType(result));
360 if (!structure || !structure.getParams()) {
361 if (failed(collectTypes(call, result))) {
362 return failure();
363 }
364 continue;
365 }
366 auto concrete = typeResolver.resolveAffineStructArguments(
367 structure, [this, &call, &env, &group](AffineMapAttr map) -> Attribute {
368 if (group >= call.getMapOperands().size()) {
369 call.emitError("missing affine specialization operands");
370 return {};
371 }
372 SmallVector<Attribute> operands;
373 for (Value operand : call.getMapOperands()[group++]) {
374 operands.push_back(env.lookup(operand).constant);
375 }
376 Attribute argument = typeResolver.resolveAffineArgument(map, operands);
377 if (!argument) {
378 call.emitError(
379 "monomorphize-templates: cannot resolve affine struct specialization argument"
380 );
381 }
382 return argument;
383 }
384 );
385 if (failed(concrete)) {
386 return failure();
387 }
388 bool rolledResult = hasDirectAffineArgument(structure);
389 if (rolledResult) {
390 if (failed(recordFamilyCandidate(call, structure, *concrete))) {
391 return failure();
392 }
393 }
394 auto kind = rolledResult ? StructSpecializationRequest::Kind::RolledCallResult
395 : StructSpecializationRequest::Kind::Plain;
396 if (failed(recordRequest(call, *concrete, {}, {}, kind))) {
397 return failure();
398 }
399 }
400 for (Type argument : call.getArgOperands().getTypes()) {
401 if (auto structure = dyn_cast<StructType>(typeResolver.resolveType(argument));
402 structure && hasDirectAffineArgument(structure)) {
403 if (seenRolledCalls.insert({call, structure}).second) {
404 rolledCalls.emplace_back(call, structure);
405 }
406 }
407 }
408 return success();
409 }
410
412 static std::optional<int64_t> getKnownInteger(const DiscoveryValue &fact) {
413 auto value = dyn_cast_or_null<IntegerAttr>(fact.constant);
414 if (!value || value.getValue().getSignificantBits() > 64) {
415 return std::nullopt;
416 }
417 return value.getInt();
418 }
419
421 static void bindResults(ValueRange results, ArrayRef<DiscoveryValue> values, Environment &env) {
422 for (auto [result, value] : llvm::zip_equal(results, values)) {
423 env[result] = value;
424 }
425 }
426
429 LogicalResult
430 evaluateIf(scf::IfOp branch, const Environment &env, SmallVector<DiscoveryValue> &results) {
431 auto condition = getKnownInteger(env.lookup(branch.getCondition()));
432 SmallVector<DiscoveryValue> left, right;
433 if (!condition) {
434 storage.invalidate();
435 }
436 if (!condition || *condition) {
437 if (failed(evaluateBlock(branch.getThenRegion().front(), env, left))) {
438 return failure();
439 }
440 }
441 if (!condition) {
442 storage.invalidate();
443 }
444 if ((!condition || !*condition) && !branch.getElseRegion().empty()) {
445 if (failed(evaluateBlock(branch.getElseRegion().front(), env, right))) {
446 return failure();
447 }
448 }
449 if (!condition) {
450 storage.invalidate();
451 }
452 if (condition) {
453 results = *condition ? left : right;
454 } else {
455 for (unsigned i = 0; i < results.size(); ++i) {
456 results[i] = left[i] == right[i] ? left[i] : Attribute();
457 }
458 }
459 return success();
460 }
461
464 LogicalResult
465 evaluateFor(scf::ForOp loop, const Environment &env, SmallVector<DiscoveryValue> &results) {
466 auto lower = getKnownInteger(env.lookup(loop.getLowerBound()));
467 auto upper = getKnownInteger(env.lookup(loop.getUpperBound()));
468 auto stride = getKnownInteger(env.lookup(loop.getStep()));
469 if (!lower || !upper || !stride || *stride <= 0 || loop.getUnsignedCmp()) {
470 return loop.emitError(
471 "specialization discovery requires known signed 'for' bounds and a positive step"
472 );
473 }
474 results.clear();
475 for (Value initial : loop.getInitArgs()) {
476 results.push_back(env.lookup(initial));
477 }
478 for (int64_t i = *lower; i < *upper;) {
479 auto local = env;
480 local[loop.getInductionVar()] = IntegerAttr::get(loop.getInductionVar().getType(), i);
481 bindResults(loop.getRegionIterArgs(), results, local);
482 results.clear();
483 if (failed(evaluateBlock(*loop.getBody(), std::move(local), results))) {
484 return failure();
485 }
486 if (!hasNextStaticIteration(i, *upper, *stride)) {
487 break;
488 }
489 i += *stride;
490 }
491 return success();
492 }
493
497 LogicalResult
498 evaluateWhile(scf::WhileOp loop, const Environment &env, SmallVector<DiscoveryValue> &results) {
499 if (isInvariantTrueCondition(loop.getConditionOp().getCondition())) {
500 return loop.emitError("specialization discovery cannot execute an always-true while loop");
501 }
502 SmallVector<DiscoveryValue> carried;
503 for (Value initial : loop.getInits()) {
504 carried.push_back(env.lookup(initial));
505 }
506 while (true) {
507 auto before = env;
508 bindResults(loop.getBefore().front().getArguments(), carried, before);
509 SmallVector<DiscoveryValue> forwarded;
510 Attribute condition;
511 if (failed(
512 evaluateBlock(loop.getBefore().front(), std::move(before), forwarded, &condition)
513 )) {
514 return failure();
515 }
516 auto known = dyn_cast_or_null<IntegerAttr>(condition);
517 if (!known || !known.getType().isInteger(1)) {
518 // Runtime loops need no execution when specialization requests are
519 // independent of their carried values. Visit both regions with unknown
520 // arguments; dependent affine requests still fail in discoverCallTypes.
521 storage.invalidate();
522 for (Region *region : {&loop.getBefore(), &loop.getAfter()}) {
523 auto local = env;
524 for (BlockArgument argument : region->front().getArguments()) {
525 local[argument] = {};
526 }
527 SmallVector<DiscoveryValue> ignored;
528 Attribute ignoredCondition;
529 if (failed(
530 evaluateBlock(region->front(), std::move(local), ignored, &ignoredCondition)
531 )) {
532 return failure();
533 }
534 }
535 storage.invalidate();
536 results.assign(loop.getNumResults(), DiscoveryValue());
537 return success();
538 }
539 if (known.getValue().isZero()) {
540 results = std::move(forwarded);
541 return success();
542 }
543 auto after = env;
544 bindResults(loop.getAfter().front().getArguments(), forwarded, after);
545 carried.clear();
546 if (failed(evaluateBlock(loop.getAfter().front(), std::move(after), carried))) {
547 return failure();
548 }
549 }
550 }
551
554 FailureOr<Attribute> evaluateConstRead(ConstReadOp read) {
556 read, bindings.lookup(read.getConstNameAttr()), typeResolver.resolveType(read.getType())
557 );
558 }
559
562 void
563 evaluateFoldableOp(Operation &op, const Environment &env, SmallVector<DiscoveryValue> &results) {
564 SmallVector<Attribute> operands;
565 for (Value operand : op.getOperands()) {
566 operands.push_back(env.lookup(operand).constant);
567 }
568 if (llvm::all_of(operands, [](Attribute attr) { return attr != nullptr; })) {
569 SmallVector<OpFoldResult> folded;
570 if (succeeded(evaluateScalarOperation(&op, operands, folded))) {
571 for (auto [index, value] : llvm::enumerate(folded)) {
572 if (auto attr = dyn_cast<Attribute>(value)) {
573 results[index] = attr;
574 } else {
575 results[index] = env.lookup(llvm::cast<Value>(value));
576 }
577 }
578 }
579 }
580 }
581
583 std::optional<SmallVector<Attribute>>
584 arrayCoordinates(array::ArrayAccessOpInterface access, const Environment &env) {
585 auto type =
586 llvm::cast<array::ArrayType>(typeResolver.resolveType(access.getArrRef().getType()));
587 SmallVector<int64_t> shape(type.getShape());
588 auto concreteShape = storage.getShape(env.lookup(access.getArrRef()));
589 if (!concreteShape.empty()) {
590 shape.assign(concreteShape.begin(), concreteShape.end());
591 }
592 SmallVector<Attribute> path;
593 for (auto [i, index] : llvm::enumerate(access.getIndices())) {
594 auto coordinate = getKnownInteger(env.lookup(index));
595 if (!coordinate || *coordinate < 0 || i >= shape.size() || shape[i] < 0 ||
596 *coordinate >= shape[i]) {
597 return std::nullopt;
598 }
599 path.push_back(IntegerAttr::get(IndexType::get(context()), *coordinate));
600 }
601 return path;
602 }
603
606 SmallVector<int64_t>
607 allocationShape(array::ArrayType type, llvm::function_ref<Attribute(AffineMapAttr)> resolveMap) {
608 auto resolved = llvm::cast<array::ArrayType>(typeResolver.resolveType(type));
609 SmallVector<int64_t> shape(resolved.getShape());
610 for (auto [i, dimension] : llvm::enumerate(type.getDimensionSizes())) {
611 if (auto map = dyn_cast<AffineMapAttr>(dimension)) {
612 auto extent = getKnownInteger(resolveMap(map));
613 if (extent && *extent >= 0) {
614 shape[i] = *extent;
615 }
616 }
617 }
618 return shape;
619 }
620
623 void recordPodShapes(
624 Type type, const DiscoveryValue &value, SmallVector<Attribute> &path,
625 llvm::function_ref<Attribute(AffineMapAttr)> resolveMap
626 ) {
627 if (auto podType = dyn_cast<pod::PodType>(type)) {
628 for (auto record : podType.getRecords()) {
629 path.push_back(record.getName());
630 recordPodShapes(record.getType(), value, path, resolveMap);
631 path.pop_back();
632 }
633 } else if (auto arrayType = dyn_cast<array::ArrayType>(type)) {
634 storage.setShape(value, allocationShape(arrayType, resolveMap), path);
635 } else if (auto structure = dyn_cast<StructType>(type)) {
636 if (auto parameters = structure.getParams()) {
637 for (Attribute parameter : parameters) {
638 if (auto map = dyn_cast<AffineMapAttr>(parameter)) {
639 resolveMap(map);
640 }
641 }
642 }
643 } else if (isa<TypeVarType>(type)) {
644 // Maps introduced by a binding have no operand groups on this allocation.
645 auto resolved = typeResolver.resolveType(type);
646 if (resolved != type) {
647 recordPodShapes(resolved, value, path, [](AffineMapAttr) { return Attribute(); });
648 }
649 }
650 }
651
654 bool
655 evaluateStorage(Operation &op, const Environment &env, SmallVector<DiscoveryValue> &results) {
656 return llvm::TypeSwitch<Operation *, bool>(&op)
657 .Case<array::CreateArrayOp>([&](auto allocation) {
658 auto value = storage.create();
659 results[0] = value;
660 size_t group = 0;
661 auto shape = allocationShape(allocation.getType(), [&](AffineMapAttr map) {
662 if (group >= allocation.getMapOperands().size()) {
663 return Attribute();
664 }
665 SmallVector<Attribute> operands;
666 for (Value operand : allocation.getMapOperands()[group++]) {
667 operands.push_back(env.lookup(operand).constant);
668 }
669 return typeResolver.resolveAffineArgument(map, operands);
670 });
671 storage.setShape(value, shape);
672 if (llvm::any_of(shape, [](int64_t size) { return size <= 0; })) {
673 return true;
674 }
675 for (auto [linear, element] : llvm::enumerate(allocation.getElements())) {
676 // A bound array-valued element contributes a subtree, not extra
677 // constructor coordinates. Binding its type can expand the resolved rank.
678 SmallVector<Attribute> path(allocation.getType().getDimensionSizes().size());
679 uint64_t offset = linear;
680 for (size_t i = path.size(); i > 0; --i) {
681 path[i - 1] = IntegerAttr::get(
682 IndexType::get(context()), static_cast<int64_t>(offset % shape[i - 1])
683 );
684 offset /= shape[i - 1];
685 }
686 storage.write(value, path, env.lookup(element));
687 }
688 return true;
689 })
690 .Case<pod::NewPodOp>([&](auto allocation) {
691 auto value = storage.create();
692 size_t group = 0;
693 SmallVector<Attribute> path;
694 recordPodShapes(allocation.getType(), value, path, [&](AffineMapAttr map) {
695 if (group >= allocation.getMapOperands().size()) {
696 return Attribute();
697 }
698 SmallVector<Attribute> operands;
699 for (Value operand : allocation.getMapOperands()[group++]) {
700 operands.push_back(env.lookup(operand).constant);
701 }
702 return typeResolver.resolveAffineArgument(map, operands);
703 });
704 for (auto record : allocation.getInitializedRecordValues()) {
705 storage.write(value, {StringAttr::get(context(), record.name)}, env.lookup(record.value));
706 }
707 results[0] = std::move(value);
708 return true;
709 })
710 .Case<pod::ReadPodOp>([&](auto read) {
711 results[0] = storage.read(
712 env.lookup(read.getPodRef()), {StringAttr::get(context(), read.getRecordName())},
713 isa<array::ArrayType, pod::PodType>(typeResolver.resolveType(read.getType()))
714 );
715 return true;
716 })
717 .Case<pod::WritePodOp>([&](auto write) {
718 storage.write(
719 env.lookup(write.getPodRef()), {StringAttr::get(context(), write.getRecordName())},
720 env.lookup(write.getValue())
721 );
722 return true;
723 })
724 .Case<array::ArrayAccessOpInterface>([&](auto access) {
725 auto coordinates = arrayCoordinates(access, env);
726 auto array = env.lookup(access.getArrRef());
727 if (isa<array::ReadArrayOp, array::ExtractArrayOp>(op)) {
728 if (coordinates) {
729 results[0] = storage.read(
730 array, *coordinates,
731 isa<array::ArrayType, pod::PodType>(
732 typeResolver.resolveType(op.getResult(0).getType())
733 )
734 );
735 }
736 } else if (coordinates) {
737 auto value = isa<array::WriteArrayOp>(op)
738 ? llvm::cast<array::WriteArrayOp>(op).getRvalue()
739 : llvm::cast<array::InsertArrayOp>(op).getRvalue();
740 storage.write(array, *coordinates, env.lookup(value));
741 } else {
742 storage.forget(array);
743 }
744 return true;
745 })
746 .Case<UnifiableCastOp>([&](auto conversion) {
747 results[0] = env.lookup(conversion.getInput());
748 return true;
749 }).Default([](Operation *) { return false; });
750 }
751
754 LogicalResult
755 evaluateOperation(Operation &op, const Environment &env, SmallVector<DiscoveryValue> &results) {
756 if (visitedOperations) {
757 visitedOperations->push_back(&op);
758 }
759 if (auto branch = dyn_cast<scf::IfOp>(op)) {
760 if (failed(evaluateIf(branch, env, results))) {
761 return failure();
762 }
763 } else if (auto forLoop = dyn_cast<scf::ForOp>(op)) {
764 if (failed(evaluateFor(forLoop, env, results))) {
765 return failure();
766 }
767 } else if (auto whileLoop = dyn_cast<scf::WhileOp>(op)) {
768 if (failed(evaluateWhile(whileLoop, env, results))) {
769 return failure();
770 }
771 } else if (op.getNumRegions()) {
772 return op.emitError("unsupported control flow in specialization discovery");
773 } else {
774 // Local aggregate facts supplement scalar folding; reads from external
775 // storage remain unknown. Diagnose unknown values only when needed.
776 if (evaluateStorage(op, env, results)) {
777 // Still collect nested specialization types below.
778 } else if (auto read = dyn_cast<ConstReadOp>(op)) {
779 auto value = evaluateConstRead(read);
780 if (failed(value)) {
781 return failure();
782 }
783 results[0] = *value;
784 } else if (isMemoryEffectFree(&op) || isa<cast::FeltToIndexOp, cast::IntToFeltOp>(op)) {
785 evaluateFoldableOp(op, env, results);
786 }
787 if (auto call = dyn_cast<function::CallOp>(op)) {
788 if (failed(discoverCallTypes(call, env))) {
789 return failure();
790 }
791 }
792 if (!isMemoryEffectFree(&op) &&
793 !isa<
796 for (Value operand : op.getOperands()) {
797 if (isa<array::ArrayType, pod::PodType>(typeResolver.resolveType(operand.getType()))) {
798 storage.forget(env.lookup(operand));
799 }
800 }
801 }
802 if (isa<function::CallOp>(op)) {
803 return success();
804 }
805 }
806 for (Type type : op.getResultTypes()) {
807 if (failed(collectTypes(&op, type))) {
808 return failure();
809 }
810 }
811 return success();
812 }
813
817 LogicalResult evaluateBlock(
818 Block &body, Environment env, SmallVectorImpl<DiscoveryValue> &yielded,
819 Attribute *condition = nullptr
820 ) {
821 for (Operation &op : body) {
822 if (auto terminator = dyn_cast<scf::ConditionOp>(op)) {
823 if (!condition) {
824 return op.emitError("scf.condition outside a while condition region");
825 }
826 *condition = env.lookup(terminator.getCondition()).constant;
827 for (Value operand : terminator.getArgs()) {
828 yielded.push_back(env.lookup(operand));
829 }
830 return success();
831 }
832 if (isa<YieldOp, scf::YieldOp, function::ReturnOp>(op)) {
833 for (Value operand : op.getOperands()) {
834 yielded.push_back(env.lookup(operand));
835 }
836 return success();
837 }
838 if (op.hasTrait<OpTrait::IsTerminator>()) {
839 return op.emitError("unsupported control flow in specialization discovery");
840 }
841 SmallVector<DiscoveryValue> results(op.getNumResults());
842 if (failed(evaluateOperation(op, env, results))) {
843 return failure();
844 }
845 bindResults(op.getResults(), results, env);
846 }
847 return success();
848 }
849
850public:
851 MLIRContext *context() { return source->getContext(); }
852
853 DiscoveryRun(
854 Operation *definition, const StructSpecializationDiscovery::Bindings &parameters,
855 SmallVectorImpl<Operation *> *visited,
857 )
858 : source(definition), bindings(parameters), typeResolver(bindings),
859 visitedOperations(visited), callTargets(targets) {}
860
862 FailureOr<StructSpecializationDiscovery::Bindings> evaluateBindings() {
863 if (auto templ = source->getParentOfType<TemplateOp>()) {
864 for (auto expr : templ.getConstOps<TemplateExprOp>()) {
865 SmallVector<DiscoveryValue> values;
866 if (failed(evaluateBlock(expr.getInitializerRegion().front(), Environment(), values))) {
867 return failure();
868 }
869 if (values.size() == 1 && values[0].constant) {
870 bindings[FlatSymbolRefAttr::get(expr.getSymNameAttr())] = values[0].constant;
871 }
872 }
873 }
874 return bindings;
875 }
876
879 FailureOr<StructSpecializationDiscovery::Requests> run() {
880 if (failed(evaluateBindings())) {
881 return failure();
882 }
883 if (auto structure = dyn_cast<StructDefOp>(source)) {
884 for (auto member : structure.getOps<MemberDefOp>()) {
885 if (failed(collectTypes(member, member.getType()))) {
886 return failure();
887 }
888 }
889 }
890 SmallVector<function::FuncDefOp> functions;
891 if (auto structure = dyn_cast<StructDefOp>(source)) {
892 llvm::append_range(functions, structure.getOps<function::FuncDefOp>());
893 } else {
894 functions.push_back(llvm::cast<function::FuncDefOp>(source));
895 }
896 for (auto function : functions) {
897 if (!function.getBody().hasOneBlock()) {
898 return function.emitError(
899 "specialization discovery requires a defined single-block function; "
900 "unstructured control flow is unsupported"
901 );
902 }
903 for (Type type : function.getFunctionType().getInputs()) {
904 if (failed(collectTypes(function, type))) {
905 return failure();
906 }
907 }
908 for (Type type : function.getFunctionType().getResults()) {
909 if (failed(collectTypes(function, type))) {
910 return failure();
911 }
912 }
913 SmallVector<DiscoveryValue> values;
914 if (failed(evaluateBlock(function.getBody().front(), Environment(), values))) {
915 return failure();
916 }
917 }
918 for (auto [call, family] : rolledCalls) {
919 auto key = getFamilyKey(call, family);
920 if (failed(key)) {
921 return failure();
922 }
923 if (!knownFamilies.contains(*key)) {
924 return call.emitError("no known source for affine struct call argument ") << family;
925 }
926 auto candidates = familyCandidates.lookup(*key);
927 for (StructType candidate : candidates) {
928 if (failed(recordRequest(call, candidate))) {
929 return failure();
930 }
931 }
932 if (callTargets) {
933 callTargets->push_back({call, candidates});
934 }
935 }
936 return std::move(requests);
937 }
938};
939
940} // namespace
941
943 ConstReadOp read, Attribute binding, Type readType
944) {
945 if (!binding) {
946 return binding;
947 }
948 if (auto constant = dyn_cast<felt::FeltConstAttr>(binding)) {
949 if (constant.getType() != readType) {
950 return read.emitError("cannot read felt template binding ")
951 << read.getConstNameAttr() << " of type " << constant.getType() << " as " << readType
952 << (isa<felt::FeltType>(readType) ? "; read the binding using its declared felt type"
953 : "; use an explicit cast");
954 }
955 if (auto reduced = constant.getReducedValue()) {
956 return Attribute(felt::FeltConstAttr::get(read.getContext(), *reduced, constant.getType()));
957 }
958 return binding;
959 }
960 if (auto integer = dyn_cast<IntegerAttr>(binding)) {
961 const APInt &number = integer.getValue();
962 if (auto type = dyn_cast<felt::FeltType>(readType)) {
963 // An i1's set bit represents one; index values have signed semantics.
964 auto value = integer.getType().isSignlessInteger(1)
965 ? llvm::DynamicAPInt(number.isOne() ? 1 : 0)
966 : llvm::DynamicAPInt(number);
967 if (type.hasField()) {
968 const Field &field = type.getField();
969 return Attribute(felt::FeltConstAttr::get(read.getContext(), field.reduce(value), type));
970 }
971 if (value < 0) {
972 return read.emitError("cannot read negative template binding ")
973 << read.getConstNameAttr() << " as felt without a known field modulus";
974 }
975 return Attribute(felt::FeltConstAttr::get(read.getContext(), value, type));
976 }
977 if (readType.isSignlessInteger(1)) {
978 if (!number.isZero() && !number.isOne()) {
979 return read.emitError("cannot read template binding ")
980 << read.getConstNameAttr() << " with value " << binding
981 << " as i1: expected zero or one; use an explicit cast";
982 }
983 return Attribute(IntegerAttr::get(readType, number.isOne() ? 1 : 0));
984 }
985 if (readType.isIndex()) {
986 auto bits = integer.getType().isSignlessInteger(1) ? number.zext(64) : number.sextOrTrunc(64);
987 return Attribute(IntegerAttr::get(readType, bits));
988 }
989 }
990 return read.emitError("cannot read template binding ")
991 << read.getConstNameAttr() << " with value \"" << binding << "\" as type " << readType;
992}
993
994FailureOr<StructSpecializationDiscovery::Bindings>
996 return DiscoveryRun(source, bindings, nullptr).evaluateBindings();
997}
998
999FailureOr<StructSpecializationDiscovery::Requests> StructSpecializationDiscovery::discover(
1000 Operation *source, const Bindings &bindings, SmallVectorImpl<Operation *> *visitedOperations,
1001 CallTargets *callTargets
1002) const {
1003 return DiscoveryRun(source, bindings, visitedOperations, callTargets).run();
1004}
This file implements helpers for DynamicAPInt operations and conversions that LLVM does not provide.
Apache License January AND DISTRIBUTION Definitions License shall mean the terms and conditions for and distribution as defined by Sections through of this document Licensor shall mean the copyright owner or entity authorized by the copyright owner that is granting the License Legal Entity shall mean the union of the acting entity and all other entities that control are controlled by or are under common control with that entity For the purposes of this definition control direct or to cause the direction or management of such whether by contract or including but not limited to software source documentation and configuration files Object form shall mean any form resulting from mechanical transformation or translation of a Source including but not limited to compiled object generated and conversions to other media types Work shall mean the work of whether in Source or Object made available under the as indicated by a copyright notice that is included in or attached to the whether in Source or Object that is based or other modifications as a an original work of authorship For the purposes of this Derivative Works shall not include works that remain separable or merely the Work and Derivative Works thereof Contribution shall mean any work of including the original version of the Work and any modifications or additions to that Work or Derivative Works that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner For the purposes of this definition
Definition LICENSE.txt:53
Apache License January AND DISTRIBUTION Definitions License shall mean the terms and conditions for and distribution as defined by Sections through of this document Licensor shall mean the copyright owner or entity authorized by the copyright owner that is granting the License Legal Entity shall mean the union of the acting entity and all other entities that control are controlled by or are under common control with that entity For the purposes of this definition control direct or to cause the direction or management of such whether by contract or including but not limited to software source documentation source
Definition LICENSE.txt:28
::mlir::Operation::operand_range getIndices()
Gets the operand range containing the index for each dimension.
::mlir::TypedValue<::llzk::array::ArrayType > getArrRef()
Gets the SSA Value for the referenced array.
ArrayType cloneWith(std::optional<::llvm::ArrayRef< int64_t > > shape, ::mlir::Type elementType) const
Clone this type with the given shape and element type.
::mlir::Type getElementType() const
::llvm::ArrayRef<::mlir::Attribute > getDimensionSizes() const
::mlir::SymbolRefAttr getNameRef() const
static StructType get(::mlir::SymbolRefAttr structName)
Definition Types.cpp.inc:81
::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::Operation::operand_range getArgOperands()
Definition Ops.h.inc:430
::mlir::OperandRangeRange getMapOperands()
Definition Ops.h.inc:434
::mlir::FlatSymbolRefAttr getConstNameAttr()
Definition Ops.h.inc:599
::mlir::FlatSymbolRefAttr getNameRef() const
void write(const DiscoveryValue &destination, llvm::ArrayRef< mlir::Attribute > path, const DiscoveryValue &value)
Replace a subtree, clearing old facts even when the new value is unknown.
DiscoveryValue read(const DiscoveryValue &source, llvm::ArrayRef< mlir::Attribute > path, bool aggregate)
Read a scalar leaf or snapshot an aggregate subtree into an independent root.
void forget(const DiscoveryValue &value)
Forget the contents of a potentially modified or escaping local aggregate.
mlir::FailureOr< Requests > discover(mlir::Operation *source, const Bindings &bindings, llvm::SmallVectorImpl< mlir::Operation * > *visitedOperations=nullptr, CallTargets *callTargets=nullptr) const
Return ordered, deduplicated requests for a struct or free-function source, or diagnose an unresolved...
llvm::DenseMap< mlir::Attribute, mlir::Attribute > Bindings
mlir::FailureOr< Bindings > evaluateBindings(mlir::Operation *source, const Bindings &bindings) const
Evaluate enclosing template expressions in declaration order and return an extended copy of bindings ...
llvm::SmallVector< StructSpecializationRequest > Requests
Common interface for constant parameter-like symbol operations that can be defined within a TemplateO...
llvm::DenseMap< mlir::Value, BoundValue > Environment
mlir::LogicalResult evaluateScalarOperation(mlir::Operation *op, llvm::ArrayRef< mlir::Attribute > operands, llvm::SmallVectorImpl< mlir::OpFoldResult > &results)
Evaluate a scalar operation using caller-supplied operand constants.
bool isInvariantTrueCondition(mlir::Value condition)
Prove a Boolean condition true without any loop-carried bindings.
mlir::FailureOr< mlir::Attribute > resolveConstReadBinding(ConstReadOp read, mlir::Attribute binding, mlir::Type readType)
Materialize a binding with the resolved read type for evaluation or cloning.
bool hasNextStaticIteration(int64_t current, int64_t upper, int64_t step)
Whether another signed iteration exists after current, for current < upper and a positive step.
Definition StaticLoop.h:19
ArrayType flattenArrayElementType(ArrayType outerArrTy, Type elementType)
bool isConcreteStructParamAttr(mlir::Attribute attr, bool allowStructParams=true)
Return true if attr is a concrete argument for a parameterized struct type.
Definition TypeHelper.h:133
bool hasAffineMapAttr(Type type)