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