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