LLZK 0.1.0
Veridise's ZK Language IR
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
16#include "llzk/Util/Hash.h"
19
20#include <mlir/Analysis/DataFlow/DeadCodeAnalysis.h>
21#include <mlir/IR/Value.h>
22
23#include <llvm/Support/Debug.h>
24
25#include <numeric>
26#include <unordered_set>
27
28#define DEBUG_TYPE "llzk-cdg"
29
30using namespace mlir;
31
32namespace llzk {
33
34using namespace array;
35using namespace component;
36using namespace constrain;
37using namespace function;
38
39/* ConstrainRefAnalysis */
40
42 mlir::CallOpInterface call, dataflow::CallControlFlowAction action,
43 const ConstrainRefLattice &before, ConstrainRefLattice *after
44) {
45 LLVM_DEBUG(
46 llvm::dbgs() << "ConstrainRefAnalysis::visitCallControlFlowTransfer: " << call << '\n'
47 );
48 auto fnOpRes = resolveCallable<FuncDefOp>(tables, call);
49 ensure(succeeded(fnOpRes), "could not resolve called function");
50
51 LLVM_DEBUG({
52 llvm::dbgs().indent(4) << "parent op is ";
53 if (auto s = call->getParentOfType<StructDefOp>()) {
54 llvm::dbgs() << s.getName();
55 } else if (auto p = call->getParentOfType<FuncDefOp>()) {
56 llvm::dbgs() << p.getName();
57 } else {
58 llvm::dbgs() << "<UNKNOWN PARENT TYPE>";
59 }
60 llvm::dbgs() << '\n';
61 });
62
66 if (action == dataflow::CallControlFlowAction::EnterCallee) {
67 // We skip updating the incoming lattice for function calls,
68 // as ConstrainRefs are relative to the containing function/struct, so we don't need to pollute
69 // the callee with the callers values.
70 // This also avoids a non-convergence scenario, as calling a
71 // function from other contexts can cause the lattice values to oscillate and constantly
72 // change (thus looping infinitely).
73
74 setToEntryState(after);
75 }
79 else if (action == dataflow::CallControlFlowAction::ExitCallee) {
80 // Get the argument values of the lattice by getting the state as it would
81 // have been for the callsite.
82 const ConstrainRefLattice *beforeCall = nullptr;
83 if (auto *prev = call->getPrevNode()) {
84 beforeCall = static_cast<const ConstrainRefLattice *>(getLattice(prev));
85 } else {
86 beforeCall = static_cast<const ConstrainRefLattice *>(getLattice(call->getBlock()));
87 }
88 ensure(beforeCall, "could not get prior lattice");
89
90 // Translate argument values based on the operands given at the call site.
91 std::unordered_map<ConstrainRef, ConstrainRefLatticeValue, ConstrainRef::Hash> translation;
92 auto funcOpRes = resolveCallable<FuncDefOp>(tables, call);
93 ensure(mlir::succeeded(funcOpRes), "could not lookup called function");
94 auto funcOp = funcOpRes->get();
95
96 auto callOp = mlir::dyn_cast<CallOp>(call.getOperation());
97 ensure(callOp, "call is not a llzk::CallOp");
98
99 for (unsigned i = 0; i < funcOp.getNumArguments(); i++) {
100 auto key = ConstrainRef(funcOp.getArgument(i));
101 auto val = beforeCall->getOrDefault(callOp.getOperand(i));
102 translation[key] = val;
103 }
104
105 // The lattice at the return is:
106 // - the lattice before the call, plus
107 // - the translated, return values, plus
108 // - any translated internal values (so we can see where values are used)
109 mlir::ChangeResult updated = after->join(*beforeCall);
110 for (unsigned i = 0; i < callOp.getNumResults(); i++) {
111 auto retVal = before.getReturnValue(i);
112 auto [translatedVal, _] = retVal.translate(translation);
113 updated |= after->setValue(callOp->getResult(i), translatedVal);
114 }
115 for (const auto &[val, refVal] : before.getMap()) {
116 auto [translatedVal, _] = refVal.translate(translation);
117 updated |= after->setValue(val, translatedVal);
118 }
119 propagateIfChanged(after, updated);
120 }
125 else if (action == mlir::dataflow::CallControlFlowAction::ExternalCallee) {
126 // For external calls, we propagate what information we already have from
127 // before the call to after the call, since the external call won't invalidate
128 // any of that information. It also, conservatively, makes no assumptions about
129 // external calls and their computation, so CDG edges will not be computed over
130 // input arguments to external functions.
131 join(after, before);
132 }
133}
134
136 mlir::Operation *op, const ConstrainRefLattice &before, ConstrainRefLattice *after
137) {
138 LLVM_DEBUG(llvm::dbgs() << "ConstrainRefAnalysis::visitOperation: " << *op << '\n');
139 // Collect the references that are made by the operands to `op`.
141 for (mlir::OpOperand &operand : op->getOpOperands()) {
142 operandVals[operand.get()] = before.getOrDefault(operand.get());
143 }
144
145 // Propagate existing state.
146 join(after, before);
147
148 // Add operand values, if not already added. Ensures that the default value
149 // of a ConstrainRef (the source of the ref) is visible in the lattice.
150 ChangeResult res = after->setValues(operandVals);
151
152 // We will now join the the operand refs based on the type of operand.
153 if (auto fieldRefOp = mlir::dyn_cast<FieldRefOpInterface>(op)) {
154 // The operand is indexed into by the FieldDefOp.
155 auto fieldOpRes = fieldRefOp.getFieldDefOp(tables);
156 ensure(mlir::succeeded(fieldOpRes), "could not find field read");
157
159 if (fieldRefOp.isRead()) {
160 fieldRefRes = fieldRefOp.getVal();
161 } else {
162 fieldRefRes = fieldRefOp;
163 }
164
165 const auto &ops = operandVals.at(fieldRefOp.getComponent());
166 auto [fieldVals, _] = ops.referenceField(fieldOpRes.value());
167
168 res |= after->setValue(fieldRefRes, fieldVals);
169 } else if (auto arrayAccessOp = mlir::dyn_cast<ArrayAccessOpInterface>(op)) {
170 // Covers read/write/extract/insert array ops
171 arraySubdivisionOpUpdate(arrayAccessOp, operandVals, before, after);
172 } else if (auto createArray = mlir::dyn_cast<CreateArrayOp>(op)) {
173 // Create an array using the operand values, if they exist.
174 // Currently, the new array must either be fully initialized or uninitialized.
175
176 auto newArrayVal = ConstrainRefLatticeValue(createArray.getType().getShape());
177 // If the array is statically initialized, iterate through all operands and initialize the array
178 // value.
179 const auto &elements = createArray.getElements();
180 if (!elements.empty()) {
181 for (unsigned i = 0; i < elements.size(); i++) {
182 auto currentOp = elements[i];
183 auto &opVals = operandVals[currentOp];
184 (void)newArrayVal.getElemFlatIdx(i).setValue(opVals);
185 }
186 }
187
188 auto createArrayRes = createArray.getResult();
189
190 res |= after->setValue(createArrayRes, newArrayVal);
191 } else if (auto structNewOp = mlir::dyn_cast<CreateStructOp>(op)) {
192 auto newOpRes = structNewOp.getResult();
193 auto newStructValue = before.getOrDefault(newOpRes);
194 res |= after->setValue(newOpRes, newStructValue);
195 } else {
196 // Standard union of operands into the results value.
197 // TODO: Could perform constant computation/propagation here for, e.g., arithmetic
198 // over constants, but such analysis may be better suited for a dedicated pass.
199 res |= fallbackOpUpdate(op, operandVals, before, after);
200 }
201
202 propagateIfChanged(after, res);
203}
204
205// Perform a standard union of operands into the results value.
207 mlir::Operation *op, const ConstrainRefLattice::ValueMap &operandVals,
208 const ConstrainRefLattice &before, ConstrainRefLattice *after
209) {
210 auto updated = mlir::ChangeResult::NoChange;
211 for (auto res : op->getResults()) {
212 auto cur = before.getOrDefault(res);
213
214 for (auto &[_, opVal] : operandVals) {
215 (void)cur.update(opVal);
216 }
217 updated |= after->setValue(res, cur);
218 }
219 return updated;
220}
221
222// Perform the update for either a readarr op or an extractarr op, which
223// operate very similarly: index into the first operand using a variable number
224// of provided indices.
226 ArrayAccessOpInterface arrayAccessOp, const ConstrainRefLattice::ValueMap &operandVals,
227 const ConstrainRefLattice &before, ConstrainRefLattice *after
228) {
229 // We index the first operand by all remaining indices.
231 if (mlir::isa<ReadArrayOp, ExtractArrayOp>(arrayAccessOp)) {
232 res = arrayAccessOp->getResult(0);
233 } else {
234 res = arrayAccessOp;
235 }
236
237 auto array = arrayAccessOp.getArrRef();
238 auto it = operandVals.find(array);
239 ensure(it != operandVals.end(), "improperly constructed operandVals map");
240 auto currVals = it->second;
241
242 std::vector<ConstrainRefIndex> indices;
243
244 for (unsigned i = 0; i < arrayAccessOp.getIndices().size(); ++i) {
245 auto idxOperand = arrayAccessOp.getIndices()[i];
246 auto idxIt = operandVals.find(idxOperand);
247 ensure(idxIt != operandVals.end(), "improperly constructed operandVals map");
248 auto &idxVals = idxIt->second;
249
250 // Note: we allow constant values regardless of if they are felt or index,
251 // as if they were felt, there would need to be a cast to index, and if it
252 // was missing, there would be a semantic check failure. So we accept either
253 // so we don't have to track the cast ourselves.
254 if (idxVals.isSingleValue() && idxVals.getSingleValue().isConstant()) {
255 ConstrainRefIndex idx(idxVals.getSingleValue().getConstantValue());
256 indices.push_back(idx);
257 } else {
258 // Otherwise, assume any range is valid.
259 auto arrayType = mlir::dyn_cast<ArrayType>(array.getType());
260 auto lower = mlir::APInt::getZero(64);
261 mlir::APInt upper(64, arrayType.getDimSize(i));
262 auto idxRange = ConstrainRefIndex(lower, upper);
263 indices.push_back(idxRange);
264 }
265 }
266
267 auto [newVals, _] = currVals.extract(indices);
268
269 if (mlir::isa<ReadArrayOp, WriteArrayOp>(arrayAccessOp)) {
270 ensure(newVals.isScalar(), "array read/write must produce a scalar value");
271 }
272 // an extract operation may yield a "scalar" value if not all dimensions of
273 // the source array are instantiated; for example, if extracting an array from
274 // an input arg, the current value is a "scalar" with an array type, and extracting
275 // from that yields another single value with indices. For example: extracting [0][1]
276 // from { arg1 } yields { arg1[0][1] }.
277
278 propagateIfChanged(after, after->setValue(res, newVals));
279}
280
281/* ConstraintDependencyGraph */
282
283mlir::FailureOr<ConstraintDependencyGraph> ConstraintDependencyGraph::compute(
284 mlir::ModuleOp m, StructDefOp s, mlir::DataFlowSolver &solver, mlir::AnalysisManager &am,
285 bool runIntraprocedural
286) {
287 ConstraintDependencyGraph cdg(m, s, runIntraprocedural);
288 if (cdg.computeConstraints(solver, am).failed()) {
289 return mlir::failure();
290 }
291 return cdg;
292}
293
294void ConstraintDependencyGraph::dump() const { print(llvm::errs()); }
295
297void ConstraintDependencyGraph::print(llvm::raw_ostream &os) const {
298 // the EquivalenceClasses::iterator is sorted, but the EquivalenceClasses::member_iterator is
299 // not guaranteed to be sorted. So, we will sort members before printing them.
300 // We also want to add the constant values into the printing.
301 std::set<std::set<ConstrainRef>> sortedSets;
302 for (auto it = signalSets.begin(); it != signalSets.end(); it++) {
303 if (!it->isLeader()) {
304 continue;
305 }
306
307 std::set<ConstrainRef> sortedMembers;
308 for (auto mit = signalSets.member_begin(it); mit != signalSets.member_end(); mit++) {
309 sortedMembers.insert(*mit);
310 }
311
312 // We only want to print sets with a size > 1, because size == 1 means the
313 // signal is not in a constraint.
314 if (sortedMembers.size() > 1) {
315 sortedSets.insert(sortedMembers);
316 }
317 }
318 // Add the constants in separately.
319 for (auto &[ref, constSet] : constantSets) {
320 if (constSet.empty()) {
321 continue;
322 }
323 std::set<ConstrainRef> sortedMembers(constSet.begin(), constSet.end());
324 sortedMembers.insert(ref);
325 sortedSets.insert(sortedMembers);
326 }
327
328 os << "ConstraintDependencyGraph { ";
329
330 for (auto it = sortedSets.begin(); it != sortedSets.end();) {
331 os << "\n { ";
332 for (auto mit = it->begin(); mit != it->end();) {
333 os << *mit;
334 mit++;
335 if (mit != it->end()) {
336 os << ", ";
337 }
338 }
339
340 it++;
341 if (it == sortedSets.end()) {
342 os << " }\n";
343 } else {
344 os << " },";
345 }
346 }
347
348 os << "}\n";
349}
350
351mlir::LogicalResult ConstraintDependencyGraph::computeConstraints(
352 mlir::DataFlowSolver &solver, mlir::AnalysisManager &am
353) {
354 // Fetch the constrain function. This is a required feature for all LLZK structs.
355 FuncDefOp constrainFnOp = structDef.getConstrainFuncOp();
356 ensure(
357 constrainFnOp,
358 "malformed struct " + mlir::Twine(structDef.getName()) + " must define a constrain function"
359 );
360
366
367 // - Union all constraints from the analysis
368 // This requires iterating over all of the emit operations
369 constrainFnOp.walk([this, &solver](Operation *op) {
370 const auto *refLattice = solver.lookupState<ConstrainRefLattice>(op);
371 // aggregate the ref2Val map across operations, as some may have nested
372 // regions and blocks that aren't propagated to the function terminator
373 if (refLattice) {
374 for (auto &[ref, vals] : refLattice->getRef2Val()) {
375 ref2Val[ref].insert(vals.begin(), vals.end());
376 }
377 }
378 if (isa<EmitEqualityOp, EmitContainmentOp>(op)) {
379 this->walkConstrainOp(solver, op);
380 }
381 });
382
390 auto fnCallWalker = [this, &solver, &am](CallOp fnCall) mutable {
391 auto res = resolveCallable<FuncDefOp>(tables, fnCall);
392 ensure(mlir::succeeded(res), "could not resolve constrain call");
393
394 auto fn = res->get();
395 if (!fn.isStructConstrain()) {
396 return;
397 }
398 // Nested
399 auto calledStruct = fn.getOperation()->getParentOfType<StructDefOp>();
400 ConstrainRefRemappings translations;
401
402 auto lattice = solver.lookupState<ConstrainRefLattice>(fnCall.getOperation());
403 ensure(lattice, "could not find lattice for call operation");
404
405 // Map fn parameters to args in the call op
406 for (unsigned i = 0; i < fn.getNumArguments(); i++) {
407 auto prefix = ConstrainRef(fn.getArgument(i));
408 auto val = lattice->getOrDefault(fnCall.getOperand(i));
409 translations.push_back({prefix, val});
410 }
411 auto &childAnalysis =
412 am.getChildAnalysis<ConstraintDependencyGraphStructAnalysis>(calledStruct);
413 if (!childAnalysis.constructed()) {
414 ensure(
415 mlir::succeeded(childAnalysis.runAnalysis(solver, am, /* runIntraprocedural */ false)),
416 "could not construct CDG for child struct"
417 );
418 }
419 auto translatedCDG = childAnalysis.getResult().translate(translations);
420
421 // Now, union sets based on the translation
422 // We should be able to just merge what is in the translatedCDG to the current CDG
423 auto &tSets = translatedCDG.signalSets;
424 for (auto lit = tSets.begin(); lit != tSets.end(); lit++) {
425 if (!lit->isLeader()) {
426 continue;
427 }
428 auto leader = lit->getData();
429 for (auto mit = tSets.member_begin(lit); mit != tSets.member_end(); mit++) {
430 signalSets.unionSets(leader, *mit);
431 }
432 }
433 // And update the constant sets
434 for (auto &[ref, constSet] : translatedCDG.constantSets) {
435 constantSets[ref].insert(constSet.begin(), constSet.end());
436 }
437 };
438 if (!runIntraprocedural) {
439 constrainFnOp.walk(fnCallWalker);
440 }
441
442 return mlir::success();
443}
444
445void ConstraintDependencyGraph::walkConstrainOp(
446 mlir::DataFlowSolver &solver, mlir::Operation *emitOp
447) {
448 std::vector<ConstrainRef> signalUsages, constUsages;
449
450 const ConstrainRefLattice *refLattice = solver.lookupState<ConstrainRefLattice>(emitOp);
451 ensure(refLattice, "missing lattice for constrain op");
452
453 for (auto operand : emitOp->getOperands()) {
454 auto latticeVal = refLattice->getOrDefault(operand);
455 for (auto &ref : latticeVal.foldToScalar()) {
456 if (ref.isConstant()) {
457 constUsages.push_back(ref);
458 } else {
459 signalUsages.push_back(ref);
460 }
461 }
462 }
463
464 // Compute a transitive closure over the signals.
465 if (!signalUsages.empty()) {
466 auto it = signalUsages.begin();
467 auto leader = signalSets.getOrInsertLeaderValue(*it);
468 for (it++; it != signalUsages.end(); it++) {
469 signalSets.unionSets(leader, *it);
470 }
471 }
472 // Also update constant references for each value.
473 for (auto &sig : signalUsages) {
474 constantSets[sig].insert(constUsages.begin(), constUsages.end());
475 }
476}
477
479) const {
480 ConstraintDependencyGraph res(mod, structDef);
481 auto translate = [&translation](const ConstrainRef &elem
482 ) -> mlir::FailureOr<std::vector<ConstrainRef>> {
483 std::vector<ConstrainRef> refs;
484 for (auto &[prefix, vals] : translation) {
485 if (!elem.isValidPrefix(prefix)) {
486 continue;
487 }
488
489 if (vals.isArray()) {
490 // Try to index into the array
491 auto suffix = elem.getSuffix(prefix);
492 ensure(
493 mlir::succeeded(suffix), "failure is nonsensical, we already checked for valid prefix"
494 );
495
496 auto [resolvedVals, _] = vals.extract(suffix.value());
497 auto folded = resolvedVals.foldToScalar();
498 refs.insert(refs.end(), folded.begin(), folded.end());
499 } else {
500 for (auto &replacement : vals.getScalarValue()) {
501 auto translated = elem.translate(prefix, replacement);
502 if (mlir::succeeded(translated)) {
503 refs.push_back(translated.value());
504 }
505 }
506 }
507 }
508 if (refs.empty()) {
509 return mlir::failure();
510 }
511 return refs;
512 };
513
514 for (auto leaderIt = signalSets.begin(); leaderIt != signalSets.end(); leaderIt++) {
515 if (!leaderIt->isLeader()) {
516 continue;
517 }
518 // translate everything in this set first
519 std::vector<ConstrainRef> translatedSignals, translatedConsts;
520 for (auto mit = signalSets.member_begin(leaderIt); mit != signalSets.member_end(); mit++) {
521 auto member = translate(*mit);
522 if (mlir::failed(member)) {
523 continue;
524 }
525 for (auto &ref : *member) {
526 if (ref.isConstant()) {
527 translatedConsts.push_back(ref);
528 } else {
529 translatedSignals.push_back(ref);
530 }
531 }
532 // Also add the constants from the original CDG
533 if (auto it = constantSets.find(*mit); it != constantSets.end()) {
534 auto &origConstSet = it->second;
535 translatedConsts.insert(translatedConsts.end(), origConstSet.begin(), origConstSet.end());
536 }
537 }
538
539 if (translatedSignals.empty()) {
540 continue;
541 }
542
543 // Now we can insert the translated signals
544 auto it = translatedSignals.begin();
545 auto leader = *it;
546 res.signalSets.insert(leader);
547 for (it++; it != translatedSignals.end(); it++) {
548 res.signalSets.insert(*it);
549 res.signalSets.unionSets(leader, *it);
550 }
551
552 // And update the constant references
553 for (auto &ref : translatedSignals) {
554 res.constantSets[ref].insert(translatedConsts.begin(), translatedConsts.end());
555 }
556 }
557
558 // Translate ref2Val as well
559 for (auto &[ref, vals] : ref2Val) {
560 auto translationRes = translate(ref);
561 if (succeeded(translationRes)) {
562 for (const auto &translatedRef : *translationRes) {
563 res.ref2Val[translatedRef].insert(vals.begin(), vals.end());
564 }
565 }
566 }
567
568 return res;
569}
570
572 ConstrainRefSet res;
573 auto currRef = mlir::FailureOr<ConstrainRef>(ref);
574 while (mlir::succeeded(currRef)) {
575 // Add signals
576 for (auto it = signalSets.findLeader(*currRef); it != signalSets.member_end(); it++) {
577 if (currRef.value() != *it) {
578 res.insert(*it);
579 }
580 }
581 // Add constants
582 auto constIt = constantSets.find(*currRef);
583 if (constIt != constantSets.end()) {
584 res.insert(constIt->second.begin(), constIt->second.end());
585 }
586 // Go to parent
587 currRef = currRef->getParentPrefix();
588 }
589 return res;
590}
591
592/* ConstraintDependencyGraphStructAnalysis */
593
595 mlir::DataFlowSolver &solver, mlir::AnalysisManager &moduleAnalysisManager,
596 bool runIntraprocedural
597) {
599 getModule(), getStruct(), solver, moduleAnalysisManager, runIntraprocedural
600 );
601 if (mlir::failed(result)) {
602 return mlir::failure();
603 }
604 setResult(std::move(*result));
605 return mlir::success();
606}
607
608} // namespace llzk
This file implements (LLZK-tailored) dense data-flow analysis using the data-flow analysis framework.
void visitCallControlFlowTransfer(mlir::CallOpInterface call, dataflow::CallControlFlowAction action, const ConstrainRefLattice &before, ConstrainRefLattice *after) override
Hook for customizing the behavior of lattice propagation along the call control flow edges.
mlir::ChangeResult fallbackOpUpdate(mlir::Operation *op, const ConstrainRefLattice::ValueMap &operandVals, const ConstrainRefLattice &before, ConstrainRefLattice *after)
void setToEntryState(ConstrainRefLattice *lattice) override
Set the dense lattice at control flow entry point and propagate an update if it changed.
void arraySubdivisionOpUpdate(array::ArrayAccessOpInterface op, const ConstrainRefLattice::ValueMap &operandVals, const ConstrainRefLattice &before, ConstrainRefLattice *after)
void visitOperation(mlir::Operation *op, const ConstrainRefLattice &before, ConstrainRefLattice *after) override
Propagate constrain reference lattice values from operands to results.
Defines an index into an LLZK object.
A value at a given point of the ConstrainRefLattice.
std::pair< ConstrainRefLatticeValue, mlir::ChangeResult > translate(const TranslationMap &translation) const
For the refs contained in this value, translate them given the translation map and return the transfo...
A lattice for use in dense analysis.
mlir::DenseMap< ValueTy, ConstrainRefLatticeValue > ValueMap
mlir::ChangeResult join(const AbstractDenseLattice &rhs) override
Maximum upper bound.
ConstrainRefLatticeValue getReturnValue(unsigned i) const
mlir::ChangeResult setValues(const ValueMap &rhs)
mlir::ChangeResult setValue(ValueTy v, const ConstrainRefLatticeValue &rhs)
ConstrainRefLatticeValue getOrDefault(ValueTy v) const
const ValueMap & getMap() const
llvm::PointerUnion< mlir::Value, mlir::Operation * > ValueTy
Defines a reference to a llzk object within a constrain function call.
mlir::LogicalResult runAnalysis(mlir::DataFlowSolver &solver, mlir::AnalysisManager &moduleAnalysisManager, CDGAnalysisContext &ctx) override
Perform the analysis and construct the Result output.
static mlir::FailureOr< ConstraintDependencyGraph > compute(mlir::ModuleOp mod, component::StructDefOp s, mlir::DataFlowSolver &solver, mlir::AnalysisManager &am, bool runIntraprocedural)
Compute a ConstraintDependencyGraph (CDG)
void print(mlir::raw_ostream &os) const
Print the CDG to the specified output stream.
ConstraintDependencyGraph translate(ConstrainRefRemappings translation) const
Translate the ConstrainRefs in this CDG to that of a different context.
ConstrainRefSet getConstrainingValues(const ConstrainRef &ref) const
Get the values that are connected to the given ref via emitted constraints.
ConstraintDependencyGraph(const ConstraintDependencyGraph &other)
void dump() const
Dumps the CDG to stderr.
::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:357
void join(AbstractDenseLattice *lhs, const AbstractDenseLattice &rhs)
Join a lattice with another and propagate an update if it changed.
ConstrainRefLattice * getLattice(mlir::ProgramPoint point) override
mlir::dataflow::CallControlFlowAction CallControlFlowAction
std::vector< std::pair< ConstrainRef, ConstrainRefLatticeValue > > ConstrainRefRemappings
void ensure(bool condition, llvm::Twine errMsg)
Definition ErrorHelper.h:35
mlir::FailureOr< SymbolLookupResult< T > > resolveCallable(mlir::SymbolTableCollection &symbolTable, mlir::CallOpInterface call)
Based on mlir::CallOpInterface::resolveCallable, but using LLZK lookup helpers.