LLZK 3.0.0
An open-source IR for Zero Knowledge (ZK) circuits
Loading...
Searching...
No Matches
ConstraintDependencyGraph.cpp
Go to the documentation of this file.
1//===-- ConstraintDependencyGraph.cpp ---------------------------*- C++ -*-===//
2//
3// Part of the LLZK Project, under the Apache License v2.0.
4// See LICENSE.txt for license information.
5// Copyright 2025 Veridise Inc.
6// SPDX-License-Identifier: Apache-2.0
7//
8//===----------------------------------------------------------------------===//
9
11
17#include "llzk/Util/Hash.h"
20
21#include <mlir/Analysis/DataFlow/DeadCodeAnalysis.h>
22#include <mlir/Analysis/DataFlow/DenseAnalysis.h>
23#include <mlir/IR/Value.h>
24
25#include <llvm/ADT/STLExtras.h>
26#include <llvm/Support/Debug.h>
27
28#include <numeric>
29#include <unordered_set>
30
31#define DEBUG_TYPE "llzk-cdg"
32
33using namespace mlir;
34
35namespace llzk {
36
37using namespace array;
38using namespace component;
39using namespace constrain;
40using namespace function;
41using namespace pod;
42
43/* SourceRefAnalysis */
44
45const SourceRefAnalysis::Lattice *SourceRefAnalysis::getLattice(DataFlowSolver &solver, Value val) {
46 return solver.lookupState<Lattice>(val);
47}
48
49SourceRefLatticeValue SourceRefAnalysis::getValueState(DataFlowSolver &solver, Value val) {
50 if (const auto *state = getLattice(solver, val)) {
51 return state->getValue();
52 }
54}
55
56mlir::FailureOr<SourceRefLatticeValue>
57SourceRefAnalysis::getWriteTargetState(DataFlowSolver &solver, Operation *op) {
58 llvm::SmallDenseMap<Value, SourceRefLatticeValue, 4> operandVals;
59 for (Value operand : op->getOperands()) {
60 operandVals[operand] = getValueState(solver, operand);
61 }
62
63 SymbolTableCollection tables;
64 if (auto memberRefOp = llvm::dyn_cast<MemberRefOpInterface>(op)) {
65 if (!memberRefOp.isRead()) {
66 auto memberOpRes = memberRefOp.getMemberDefOp(tables);
67 ensure(succeeded(memberOpRes), "could not find member write");
68 auto componentIt = operandVals.find(memberRefOp.getComponent());
69 ensure(componentIt != operandVals.end(), "missing component lattice for member write");
70 auto memberValsRes = componentIt->second.referenceMember(memberOpRes.value());
71 ensure(succeeded(memberValsRes), "could not create SourceRef child for member write");
72 return memberValsRes->first;
73 }
74 }
75
76 if (auto podAccessOp = llvm::dyn_cast<PodAccessOpInterface>(op)) {
77 if (!podAccessOp.isRead()) {
78 auto podIt = operandVals.find(podAccessOp.getPodRef());
79 ensure(podIt != operandVals.end(), "missing pod lattice for pod write");
80 auto podValsRes = podIt->second.referencePodRecord(podAccessOp.getRecordNameAttr());
81 ensure(succeeded(podValsRes), "could not create SourceRef child for pod write");
82 return podValsRes->first;
83 }
84 }
85
86 if (auto arrayAccessOp = llvm::dyn_cast<ArrayAccessOpInterface>(op)) {
87 if (llvm::isa<WriteArrayOp, InsertArrayOp>(arrayAccessOp)) {
88 auto array = arrayAccessOp.getArrRef();
89 auto it = operandVals.find(array);
90 ensure(it != operandVals.end(), "improperly constructed operandVals map");
91 const auto &currVals = it->second;
92
93 std::vector<SourceRefIndex> indices;
94 for (auto [i, idxOperand] : llvm::enumerate(arrayAccessOp.getIndices())) {
95 auto idxIt = operandVals.find(idxOperand);
96 ensure(idxIt != operandVals.end(), "improperly constructed operandVals map");
97 const auto &idxVals = idxIt->second;
98
99 auto constantIndex = idxVals.getConstantValue();
100 if (succeeded(constantIndex)) {
101 indices.emplace_back(*constantIndex);
102 } else {
103 auto arrayType = llvm::dyn_cast<ArrayType>(array.getType());
104 assert(i <= std::numeric_limits<unsigned>::max() && "index too large");
105 indices.push_back(
106 SourceRefIndex::forArrayDimension(arrayType.getDimSize(static_cast<unsigned>(i)))
107 );
108 }
109 }
110
111 auto newValsRes = currVals.extract(indices);
112 ensure(succeeded(newValsRes), "could not create SourceRef child for array access");
113 auto [newVals, _] = *newValsRes;
114 if (llvm::isa<WriteArrayOp>(arrayAccessOp)) {
115 ensure(newVals.isScalar(), "array write must produce a scalar value");
116 }
117 return newVals;
118 }
119 }
120
121 return mlir::failure();
122}
123
125 if (auto value = llvm::dyn_cast_if_present<Value>(lattice->getAnchor())) {
126 if (auto arg = llvm::dyn_cast<BlockArgument>(value)) {
127 Operation *parent = arg.getOwner()->getParentOp();
128 if (llvm::isa_and_present<RegionBranchOpInterface>(parent) &&
129 llvm::isa<ArrayType, StructType, PodType>(value.getType())) {
130 // Region-branch arguments are aliases of their incoming aggregate storage. Giving them a
131 // fresh root would make loop-carried writes unstable and would discard that identity.
132 (void)lattice->setValue(SourceRefLatticeValue());
133 return;
134 }
135 }
136 (void)lattice->setValue(SourceRefLattice::getDefaultValue(value));
137 }
138}
139
141 Operation *op, ArrayRef<const Lattice *> operands, ArrayRef<Lattice *> results
142) {
143 LLVM_DEBUG(llvm::dbgs() << "SourceRefAnalysis::visitOperation: " << *op << '\n');
144
145 DenseMap<Value, const Lattice *> operandVals;
146 for (auto [operand, lattice] : llvm::zip(op->getOperands(), operands)) {
147 operandVals[operand] = lattice;
148 }
149
150 if (auto memberRefOp = llvm::dyn_cast<MemberRefOpInterface>(op)) {
151 auto memberOpRes = memberRefOp.getMemberDefOp(tables);
152 ensure(succeeded(memberOpRes), "could not find member read");
153 auto memberValsRes =
154 operandVals.at(memberRefOp.getComponent())->getValue().referenceMember(memberOpRes.value());
155 ensure(succeeded(memberValsRes), "could not create SourceRef child for member reference");
156 if (memberRefOp.isRead()) {
157 auto [memberVals, _] = *memberValsRes;
158 propagateIfChanged(results.front(), results.front()->setValue(memberVals));
159 }
160 return success();
161 }
162
163 if (auto podAccessOp = llvm::dyn_cast<PodAccessOpInterface>(op)) {
164 auto podValsRes = operandVals.at(podAccessOp.getPodRef())
165 ->getValue()
166 .referencePodRecord(podAccessOp.getRecordNameAttr());
167 ensure(succeeded(podValsRes), "could not create SourceRef child for pod reference");
168 if (podAccessOp.isRead()) {
169 auto [podVals, _] = *podValsRes;
170 propagateIfChanged(results.front(), results.front()->setValue(podVals));
171 }
172 return success();
173 }
174
175 if (auto arrayAccessOp = llvm::dyn_cast<ArrayAccessOpInterface>(op)) {
176 if (!results.empty()) {
177 auto newVals = arraySubdivisionOpUpdate(arrayAccessOp, operandVals);
178 propagateIfChanged(results.front(), results.front()->setValue(newVals));
179 }
180 return success();
181 }
182
183 if (auto createArray = llvm::dyn_cast<CreateArrayOp>(op)) {
184 auto createArrayRes = createArray.getResult();
185 const auto &elements = createArray.getElements();
186 if (elements.empty()) {
187 propagateIfChanged(
188 results.front(),
189 results.front()->setValue(SourceRef(llvm::cast<OpResult>(createArrayRes)))
190 );
191 return success();
192 }
193
194 SourceRefLatticeValue newArrayVal(createArray.getType().getShape());
195 for (auto [i, element] : llvm::enumerate(elements)) {
196 (void)newArrayVal.getElemFlatIdx(i).setValue(operandVals.at(element)->getValue());
197 }
198 propagateIfChanged(results.front(), results.front()->setValue(newArrayVal));
199 return success();
200 }
201
202 if (auto newPod = llvm::dyn_cast<NewPodOp>(op)) {
203 auto newPodValue = SourceRefLattice::getDefaultValue(newPod.getResult());
204 propagateIfChanged(results.front(), results.front()->setValue(newPodValue));
205 return success();
206 }
207
208 if (auto structNewOp = llvm::dyn_cast<CreateStructOp>(op)) {
209 auto newStructValue = SourceRefLattice::getDefaultValue(structNewOp.getResult());
210 propagateIfChanged(results.front(), results.front()->setValue(newStructValue));
211 return success();
212 }
213
214 auto updated = fallbackOpUpdate(op, operandVals, results);
215 for (Lattice *result : results) {
216 propagateIfChanged(result, updated);
217 }
218 return success();
219}
220
222 CallOpInterface call, ArrayRef<const Lattice *> operandLattices,
223 ArrayRef<Lattice *> resultLattices
224) {
225 auto callable = dyn_cast_if_present<CallableOpInterface>(call.resolveCallable());
226 if (!callable || !callable.getCallableRegion()) {
227 // Call is truly external
228 for (auto [result, lattice] : llvm::zip(call->getResults(), resultLattices)) {
229 auto resultRef = SourceRefLattice::getSourceRef(result);
230 ensure(succeeded(resultRef), "could not create external call SourceRef");
231 propagateIfChanged(lattice, lattice->setValue(*resultRef));
232 }
233 return;
234 }
235 if (resultLattices.empty()) {
236 // `verif.include` and other no-result call-like ops still need to be
237 // treated as valid callable edges, but there are no results to
238 // translate back to the caller.
239 return;
240 }
241 // Call is to a defined function with a body, but it's treated as external so we
242 // can translate the results based on the arguments.
243 auto funcOpRes = resolveCallable<FuncDefOp>(tables, call);
244 ensure(succeeded(funcOpRes), "could not lookup called function");
245 auto funcOp = funcOpRes->get();
246
247 const auto *predecessors = getOrCreateFor<mlir::dataflow::PredecessorState>(
248 getProgramPointAfter(call), getProgramPointAfter(call)
249 );
250 // If not all return sites are known, then conservatively assume we can't
251 // reason about the data-flow.
252 if (!predecessors->allPredecessorsKnown()) {
253 setAllToEntryStates(resultLattices);
254 return;
255 }
256 const auto returnSites = predecessors->getKnownPredecessors();
257
258 std::unordered_map<SourceRef, SourceRefLatticeValue, SourceRef::Hash> translation;
259 for (auto [arg, lattice] : llvm::zip_equal(funcOp.getArguments(), operandLattices)) {
260 translation[SourceRef(arg)] = lattice->getValue();
261 }
262
263 for (auto [result, resultLattice] : llvm::zip(call->getResults(), resultLattices)) {
264 SourceRefLatticeValue combined;
265 unsigned resultNum = llvm::cast<OpResult>(result).getResultNumber();
266 for (Operation *returnSite : returnSites) {
267 auto retVal = static_cast<const Lattice *>(getLatticeElementFor(
268 getProgramPointAfter(call.getOperation()),
269 returnSite->getOperand(resultNum)
270 ))
271 ->getValue();
272 auto [translatedVal, _] = retVal.translate(translation);
273 (void)combined.update(translatedVal);
274 }
275 propagateIfChanged(resultLattice, resultLattice->setValue(combined));
276 }
277}
278
280 Operation *op, const OperandValues &operandVals, ArrayRef<Lattice *> results
281) {
282 auto updated = ChangeResult::NoChange;
283 for (auto [res, lattice] : llvm::zip(op->getResults(), results)) {
284 auto cur = SourceRefLattice::getDefaultValue(res);
285 for (const auto &[_, opVal] : operandVals) {
286 (void)cur.update(opVal->getValue());
287 }
288 updated |= lattice->setValue(cur);
289 }
290 return updated;
291}
292
294 ArrayAccessOpInterface arrayAccessOp, const OperandValues &operandVals
295) {
296 auto array = arrayAccessOp.getArrRef();
297 auto it = operandVals.find(array);
298 ensure(it != operandVals.end(), "improperly constructed operandVals map");
299 const auto &currVals = it->second->getValue();
300
301 std::vector<SourceRefIndex> indices;
302 for (auto [i, idxOperand] : llvm::enumerate(arrayAccessOp.getIndices())) {
303 auto idxIt = operandVals.find(idxOperand);
304 ensure(idxIt != operandVals.end(), "improperly constructed operandVals map");
305 const auto &idxVals = idxIt->second->getValue();
306
307 auto constantIndex = idxVals.getConstantValue();
308 if (succeeded(constantIndex)) {
309 indices.emplace_back(*constantIndex);
310 } else {
311 auto arrayType = llvm::dyn_cast<ArrayType>(array.getType());
312 assert(i <= std::numeric_limits<unsigned>::max() && "index too large");
313 indices.push_back(
314 SourceRefIndex::forArrayDimension(arrayType.getDimSize(static_cast<unsigned>(i)))
315 );
316 }
317 }
318
319 auto newValsRes = currVals.extract(indices);
320 ensure(succeeded(newValsRes), "could not create SourceRef child for array access");
321 auto [newVals, _] = *newValsRes;
322 if (llvm::isa<ReadArrayOp, WriteArrayOp>(arrayAccessOp)) {
323 ensure(newVals.isScalar(), "array read/write must produce a scalar value");
324 }
325 return newVals;
326}
327
328/* ConstraintDependencyGraph */
329
330FailureOr<ConstraintDependencyGraph> ConstraintDependencyGraph::compute(
331 ModuleOp m, StructDefOp s, DataFlowSolver &solver, AnalysisManager &am,
332 const CDGAnalysisContext &ctx
333) {
334 ConstraintDependencyGraph cdg(m, s, ctx);
335 if (cdg.computeConstraints(solver, am).failed()) {
336 return mlir::failure();
337 }
338 return cdg;
339}
340
341void ConstraintDependencyGraph::dump() const { print(llvm::errs()); }
342
344void ConstraintDependencyGraph::print(llvm::raw_ostream &os) const {
345 // the EquivalenceClasses::iterator is sorted, but the EquivalenceClasses::member_iterator is
346 // not guaranteed to be sorted. So, we will sort members before printing them.
347 // We also want to add the constant values into the printing.
348 std::set<std::set<SourceRef>> sortedSets;
349 for (const auto *entry : signalSets) {
350 if (!entry->isLeader()) {
351 continue;
352 }
353
354 auto members = signalSets.members(*entry);
355 std::set<SourceRef> sortedMembers(members.begin(), members.end());
356
357 // We only want to print sets with a size > 1, because size == 1 means the
358 // signal is not in a constraint.
359 if (sortedMembers.size() > 1) {
360 sortedSets.insert(sortedMembers);
361 }
362 }
363 // Add the constants in separately.
364 for (const auto &[ref, constSet] : constantSets) {
365 if (constSet.empty()) {
366 continue;
367 }
368 std::set<SourceRef> sortedMembers(constSet.begin(), constSet.end());
369 sortedMembers.insert(ref);
370 sortedSets.insert(sortedMembers);
371 }
372
373 os << "ConstraintDependencyGraph { ";
374
375 llvm::interleave(sortedSets, os, [&](const auto &members) {
376 os << "\n { ";
377 llvm::interleaveComma(members, os);
378 os << " }";
379 }, ",");
380 if (!sortedSets.empty()) {
381 os << '\n';
382 }
383
384 os << "}\n";
385}
386
387mlir::LogicalResult ConstraintDependencyGraph::computeConstraints(
388 mlir::DataFlowSolver &solver, mlir::AnalysisManager &am
389) {
390 // Fetch the constrain function. This is a required feature for all LLZK structs.
391 FuncDefOp constrainFnOp = structDef.getConstrainFuncOp();
392 ensure(
393 constrainFnOp,
394 "malformed struct " + mlir::Twine(structDef.getName()) + " must define a constrain function"
395 );
396
402
403 // - Union all constraints from the analysis
404 // This requires iterating over all of the emit operations
405 constrainFnOp.walk([this, &solver](Operation *op) {
406 if (!dataflow::isOperationLive(solver, op)) {
407 return;
408 }
409
410 for (Value operand : op->getOperands()) {
411 auto operandRefs = SourceRefAnalysis::getValueState(solver, operand).foldToScalar();
412 for (const SourceRef &ref : operandRefs) {
413 ref2Val[ref].insert(operand);
414 }
415 }
416 for (Value result : op->getResults()) {
417 auto resultRefs = SourceRefAnalysis::getValueState(solver, result).foldToScalar();
418 for (const SourceRef &ref : resultRefs) {
419 ref2Val[ref].insert(result);
420 }
421 }
422 auto writeTargetState = SourceRefAnalysis::getWriteTargetState(solver, op);
423 if (succeeded(writeTargetState)) {
424 for (const SourceRef &ref : writeTargetState->foldToScalar()) {
425 ref2Val[ref].insert(op);
426 }
427 }
428 if (isa<EmitEqualityOp, EmitContainmentOp>(op)) {
429 this->walkConstrainOp(solver, op);
430 }
431 });
432
440 auto fnCallWalker = [this, &solver, &am](CallOp fnCall) mutable {
441 if (!dataflow::isOperationLive(solver, fnCall.getOperation())) {
442 return;
443 }
444 auto res = resolveCallable<FuncDefOp>(tables, fnCall);
445 ensure(mlir::succeeded(res), "could not resolve constrain call");
446
447 auto fn = res->get();
448 if (!fn.isStructConstrain()) {
449 return;
450 }
451 // Nested
452 auto calledStruct = fn.getOperation()->getParentOfType<StructDefOp>();
453 SourceRefRemappings translations;
454
455 // Map fn parameters to args in the call op
456 for (auto [arg, operand] : llvm::zip_equal(fn.getArguments(), fnCall.getArgOperands())) {
457 translations.push_back({SourceRef(arg), SourceRefAnalysis::getValueState(solver, operand)});
458 }
459 auto &childAnalysis =
460 am.getChildAnalysis<ConstraintDependencyGraphStructAnalysis>(calledStruct);
461 if (!childAnalysis.constructed(ctx)) {
462 ensure(
463 mlir::succeeded(childAnalysis.runAnalysis(solver, am, {.runIntraprocedural = false})),
464 "could not construct CDG for child struct"
465 );
466 }
467 auto translatedCDG = childAnalysis.getResult(ctx).translate(translations);
468 // Update the refMap with the translation
469 const auto &translatedRef2Val = translatedCDG.getRef2Val();
470 ref2Val.insert(translatedRef2Val.begin(), translatedRef2Val.end());
471
472 // Now, union sets based on the translation
473 // We should be able to just merge what is in the translatedCDG to the current CDG
474 auto &tSets = translatedCDG.signalSets;
475 for (const auto *entry : tSets) {
476 if (!entry->isLeader()) {
477 continue;
478 }
479 auto leader = entry->getData();
480 for (const SourceRef &member : tSets.members(*entry)) {
481 signalSets.unionSets(leader, member);
482 }
483 }
484 // And update the constant sets
485 for (auto &[ref, constSet] : translatedCDG.constantSets) {
486 constantSets[ref].insert(constSet.begin(), constSet.end());
487 }
488 };
489 if (!ctx.runIntraproceduralAnalysis()) {
490 constrainFnOp.walk(fnCallWalker);
491 }
492
493 return mlir::success();
494}
495
496void ConstraintDependencyGraph::walkConstrainOp(
497 mlir::DataFlowSolver &solver, mlir::Operation *emitOp
498) {
499 std::vector<SourceRef> signalUsages, constUsages;
500
501 for (auto operand : emitOp->getOperands()) {
502 auto latticeVal = SourceRefAnalysis::getValueState(solver, operand);
503 for (const auto &ref : latticeVal.foldToScalar()) {
504 if (ref.isConstant()) {
505 constUsages.push_back(ref);
506 } else {
507 signalUsages.push_back(ref);
508 }
509 }
510 }
511
512 // Compute a transitive closure over the signals.
513 if (!signalUsages.empty()) {
514 auto leader = signalSets.getOrInsertLeaderValue(signalUsages.front());
515 for (const SourceRef &signal : llvm::drop_begin(signalUsages)) {
516 signalSets.unionSets(leader, signal);
517 }
518 }
519 // Also update constant references for each value.
520 for (auto &sig : signalUsages) {
521 constantSets[sig].insert(constUsages.begin(), constUsages.end());
522 }
523}
524
527 ConstraintDependencyGraph res(mod, structDef, ctx);
528 auto translate =
529 [&translation](const SourceRef &elem) -> mlir::FailureOr<std::vector<SourceRef>> {
530 std::vector<SourceRef> refs;
531 for (auto &[prefix, vals] : translation) {
532 if (!elem.isValidPrefix(prefix)) {
533 continue;
534 }
535
536 if (vals.isArray()) {
537 // Try to index into the array
538 auto suffix = elem.getSuffix(prefix);
539 ensure(
540 mlir::succeeded(suffix), "failure is nonsensical, we already checked for valid prefix"
541 );
542
543 auto resolvedValsRes = vals.extract(suffix.value());
544 ensure(succeeded(resolvedValsRes), "could not create SourceRef child while resolving refs");
545 auto [resolvedVals, _] = *resolvedValsRes;
546 auto folded = resolvedVals.foldToScalar();
547 refs.insert(refs.end(), folded.begin(), folded.end());
548 } else {
549 for (const auto &replacement : vals.getScalarValue()) {
550 auto translated = elem.translate(prefix, replacement);
551 if (mlir::succeeded(translated)) {
552 refs.push_back(translated.value());
553 }
554 }
555 }
556 }
557 if (refs.empty()) {
558 return mlir::failure();
559 }
560 return refs;
561 };
562
563 for (const auto *entry : signalSets) {
564 if (!entry->isLeader()) {
565 continue;
566 }
567 // translate everything in this set first
568 std::vector<SourceRef> translatedSignals, translatedConsts;
569 for (const SourceRef &member : signalSets.members(*entry)) {
570 auto translated = translate(member);
571 if (mlir::failed(translated)) {
572 continue;
573 }
574 for (const auto &ref : *translated) {
575 if (ref.isConstant()) {
576 translatedConsts.push_back(ref);
577 } else {
578 translatedSignals.push_back(ref);
579 }
580 }
581 // Also add the constants from the original CDG
582 if (auto it = constantSets.find(member); it != constantSets.end()) {
583 const auto &origConstSet = it->second;
584 translatedConsts.insert(translatedConsts.end(), origConstSet.begin(), origConstSet.end());
585 }
586 }
587
588 if (translatedSignals.empty()) {
589 continue;
590 }
591
592 // Now we can insert the translated signals
593 auto leader = translatedSignals.front();
594 res.signalSets.insert(leader);
595 for (const SourceRef &signal : llvm::drop_begin(translatedSignals)) {
596 res.signalSets.insert(signal);
597 res.signalSets.unionSets(leader, signal);
598 }
599
600 // And update the constant references
601 for (auto &ref : translatedSignals) {
602 res.constantSets[ref].insert(translatedConsts.begin(), translatedConsts.end());
603 }
604 }
605
606 // Translate ref2Val as well
607 for (const auto &[ref, vals] : ref2Val) {
608 auto translationRes = translate(ref);
609 if (succeeded(translationRes)) {
610 for (const auto &translatedRef : *translationRes) {
611 res.ref2Val[translatedRef].insert(vals.begin(), vals.end());
612 }
613 }
614 }
615
616 return res;
617}
618
620 SourceRefSet res;
621 auto currRef = mlir::FailureOr<SourceRef>(ref);
622 while (mlir::succeeded(currRef)) {
623 // A dynamic access is represented by a half-open range. Match every concrete element and
624 // range that overlaps the queried path, as well as exact references.
625 for (const auto *candidate : signalSets) {
626 const SourceRef &candidateRef = candidate->getData();
627 if (!candidateRef.overlaps(*currRef)) {
628 continue;
629 }
630 for (const SourceRef &member : signalSets.members(candidateRef)) {
631 if (!member.overlaps(ref)) {
632 res.insert(member);
633 }
634 }
635 auto constIt = constantSets.find(candidateRef);
636 if (constIt != constantSets.end()) {
637 res.insert(constIt->second.begin(), constIt->second.end());
638 }
639 }
640 // Go to parent
641 currRef = currRef->getParentPrefix();
642 }
643 return res;
644}
645
646/* ConstraintDependencyGraphStructAnalysis */
647
649 mlir::DataFlowSolver &solver, mlir::AnalysisManager &moduleAnalysisManager,
650 const CDGAnalysisContext &ctx
651) {
653 getModule(), getStruct(), solver, moduleAnalysisManager, ctx
654 );
655 if (mlir::failed(result)) {
656 return mlir::failure();
657 }
658 setResult(ctx, std::move(*result));
659 return mlir::success();
660}
661
662} // namespace llzk
mlir::LogicalResult runAnalysis(mlir::DataFlowSolver &solver, mlir::AnalysisManager &moduleAnalysisManager, const CDGAnalysisContext &ctx) override
Construct a CDG, using the module's analysis manager to query ConstraintDependencyGraph objects for n...
A dependency graph of constraints enforced by an LLZK struct.
void print(mlir::raw_ostream &os) const
Print the CDG to the specified output stream.
ConstraintDependencyGraph(const ConstraintDependencyGraph &other)
static mlir::FailureOr< ConstraintDependencyGraph > compute(mlir::ModuleOp mod, component::StructDefOp s, mlir::DataFlowSolver &solver, mlir::AnalysisManager &am, const CDGAnalysisContext &ctx)
Compute a ConstraintDependencyGraph (CDG).
SourceRefSet getConstrainingValues(const SourceRef &ref) const
Get the values that are connected to the given ref via emitted constraints.
void dump() const
Dumps the CDG to stderr.
ConstraintDependencyGraph translate(SourceRefRemappings translation) const
Translate the SourceRefs in this CDG to that of a different context.
static mlir::ChangeResult fallbackOpUpdate(mlir::Operation *op, const OperandValues &operandVals, mlir::ArrayRef< Lattice * > results)
void visitExternalCall(mlir::CallOpInterface call, mlir::ArrayRef< const Lattice * > argumentLattices, mlir::ArrayRef< Lattice * > resultLattices) override
Propagate references across a call treated as external.
static mlir::FailureOr< SourceRefLatticeValue > getWriteTargetState(mlir::DataFlowSolver &solver, mlir::Operation *op)
static SourceRefLatticeValue arraySubdivisionOpUpdate(array::ArrayAccessOpInterface op, const OperandValues &operandVals)
static SourceRefLatticeValue getValueState(mlir::DataFlowSolver &solver, mlir::Value val)
void setToEntryState(Lattice *lattice) override
Set the given lattice element(s) at control-flow entry point(s).
mlir::LogicalResult visitOperation(mlir::Operation *op, mlir::ArrayRef< const Lattice * > operands, mlir::ArrayRef< Lattice * > results) override
Propagate SourceRef lattice values through an operation.
static const Lattice * getLattice(mlir::DataFlowSolver &solver, mlir::Value val)
mlir::DenseMap< mlir::Value, const Lattice * > OperandValues
static SourceRefIndex forArrayDimension(int64_t size)
Select every nonnegative index in a static or dynamic array dimension.
A value at a given point of the SourceRefLattice.
mlir::ChangeResult setValue(const LatticeValue &newValue)
static SourceRefLatticeValue getDefaultValue(ValueTy v)
static mlir::FailureOr< SourceRef > getSourceRef(mlir::Value val)
If val is the source of other values (i.e., a block argument, an allocation-like op result,...
A reference to a "source", which is the base value from which other SSA values are derived.
Definition SourceRef.h:152
bool overlaps(const SourceRef &rhs) const
Return true when both references select overlapping storage at the same path depth.
void setResult(const CDGAnalysisContext &ctx, ConstraintDependencyGraph &&r)
::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.
::llzk::function::FuncDefOp getConstrainFuncOp()
Gets the FuncDefOp that defines the constrain function in this structure, if present,...
Definition Ops.cpp:470
ScalarTy foldToScalar() const
If this is an array value, combine all elements into a single scalar value and return it.
mlir::ChangeResult setValue(const AbstractLatticeValue &rhs)
Sets this value to be equal to rhs.
mlir::ChangeResult update(const Derived &rhs)
Union this value with that of rhs.
const Derived & getElemFlatIdx(size_t i) const
Directly index into the flattened array using a single index.
const SourceRefLattice * getLatticeElementFor(mlir::ProgramPoint *point, mlir::Value value)
void setAllToEntryStates(mlir::ArrayRef< SourceRefLattice * > lattices)
bool isOperationLive(DataFlowSolver &solver, Operation *op)
std::vector< std::pair< SourceRef, SourceRefLatticeValue > > SourceRefRemappings
void ensure(bool condition, const llvm::Twine &errMsg)
mlir::FailureOr< SymbolLookupResult< T > > resolveCallable(mlir::SymbolTableCollection &symbolTable, mlir::CallOpInterface call)
Based on mlir::CallOpInterface::resolveCallable, but using LLZK lookup helpers.
Parameters and shared objects to pass to child analyses.