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