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