21#include <mlir/Analysis/DataFlow/DeadCodeAnalysis.h>
22#include <mlir/Analysis/DataFlow/DenseAnalysis.h>
23#include <mlir/IR/Value.h>
25#include <llvm/ADT/STLExtras.h>
26#include <llvm/Support/Debug.h>
29#include <unordered_set>
31#define DEBUG_TYPE "llzk-cdg"
38using namespace component;
39using namespace constrain;
40using namespace function;
46 return solver.lookupState<
Lattice>(val);
50 if (
const auto *state =
getLattice(solver, val)) {
51 return state->getValue();
56mlir::FailureOr<SourceRefLatticeValue>
58 llvm::SmallDenseMap<Value, SourceRefLatticeValue, 4> operandVals;
59 for (Value operand : op->getOperands()) {
63 SymbolTableCollection tables;
64 if (
auto memberRefOp = llvm::dyn_cast<MemberRefOpInterface>(op)) {
65 if (!memberRefOp.isRead()) {
66 auto memberOpRes = memberRefOp.getMemberDefOp(tables);
67 ensure(succeeded(memberOpRes),
"could not find member write");
68 auto componentIt = operandVals.find(memberRefOp.getComponent());
69 ensure(componentIt != operandVals.end(),
"missing component lattice for member write");
70 auto memberValsRes = componentIt->second.referenceMember(memberOpRes.value());
71 ensure(succeeded(memberValsRes),
"could not create SourceRef child for member write");
72 return memberValsRes->first;
76 if (
auto podAccessOp = llvm::dyn_cast<PodAccessOpInterface>(op)) {
77 if (!podAccessOp.isRead()) {
78 auto podIt = operandVals.find(podAccessOp.getPodRef());
79 ensure(podIt != operandVals.end(),
"missing pod lattice for pod write");
80 auto podValsRes = podIt->second.referencePodRecord(podAccessOp.getRecordNameAttr());
81 ensure(succeeded(podValsRes),
"could not create SourceRef child for pod write");
82 return podValsRes->first;
86 if (
auto arrayAccessOp = llvm::dyn_cast<ArrayAccessOpInterface>(op)) {
87 if (llvm::isa<WriteArrayOp, InsertArrayOp>(arrayAccessOp)) {
88 auto array = arrayAccessOp.getArrRef();
89 auto it = operandVals.find(
array);
90 ensure(it != operandVals.end(),
"improperly constructed operandVals map");
91 const auto &currVals = it->second;
93 std::vector<SourceRefIndex> indices;
94 for (
auto [i, idxOperand] : llvm::enumerate(arrayAccessOp.getIndices())) {
95 auto idxIt = operandVals.find(idxOperand);
96 ensure(idxIt != operandVals.end(),
"improperly constructed operandVals map");
97 const auto &idxVals = idxIt->second;
99 auto constantIndex = idxVals.getConstantValue();
100 if (succeeded(constantIndex)) {
101 indices.emplace_back(*constantIndex);
103 auto arrayType = llvm::dyn_cast<ArrayType>(
array.getType());
104 assert(i <= std::numeric_limits<unsigned>::max() &&
"index too large");
111 auto newValsRes = currVals.extract(indices);
112 ensure(succeeded(newValsRes),
"could not create SourceRef child for array access");
113 auto [newVals, _] = *newValsRes;
114 if (llvm::isa<WriteArrayOp>(arrayAccessOp)) {
115 ensure(newVals.isScalar(),
"array write must produce a scalar value");
121 return mlir::failure();
125 if (
auto value = llvm::dyn_cast_if_present<Value>(lattice->getAnchor())) {
126 if (
auto arg = llvm::dyn_cast<BlockArgument>(value)) {
127 Operation *parent = arg.getOwner()->getParentOp();
128 if (llvm::isa_and_present<RegionBranchOpInterface>(parent) &&
129 llvm::isa<ArrayType, StructType, PodType>(value.getType())) {
141 Operation *op, ArrayRef<const Lattice *> operands, ArrayRef<Lattice *> results
143 LLVM_DEBUG(llvm::dbgs() <<
"SourceRefAnalysis::visitOperation: " << *op <<
'\n');
145 DenseMap<Value, const Lattice *> operandVals;
146 for (
auto [operand, lattice] : llvm::zip(op->getOperands(), operands)) {
147 operandVals[operand] = lattice;
150 if (
auto memberRefOp = llvm::dyn_cast<MemberRefOpInterface>(op)) {
151 auto memberOpRes = memberRefOp.getMemberDefOp(tables);
152 ensure(succeeded(memberOpRes),
"could not find member read");
154 operandVals.at(memberRefOp.getComponent())->getValue().referenceMember(memberOpRes.value());
155 ensure(succeeded(memberValsRes),
"could not create SourceRef child for member reference");
156 if (memberRefOp.isRead()) {
157 auto [memberVals, _] = *memberValsRes;
158 propagateIfChanged(results.front(), results.front()->setValue(memberVals));
163 if (
auto podAccessOp = llvm::dyn_cast<PodAccessOpInterface>(op)) {
164 auto podValsRes = operandVals.at(podAccessOp.getPodRef())
166 .referencePodRecord(podAccessOp.getRecordNameAttr());
167 ensure(succeeded(podValsRes),
"could not create SourceRef child for pod reference");
168 if (podAccessOp.isRead()) {
169 auto [podVals, _] = *podValsRes;
170 propagateIfChanged(results.front(), results.front()->setValue(podVals));
175 if (
auto arrayAccessOp = llvm::dyn_cast<ArrayAccessOpInterface>(op)) {
176 if (!results.empty()) {
178 propagateIfChanged(results.front(), results.front()->setValue(newVals));
183 if (
auto createArray = llvm::dyn_cast<CreateArrayOp>(op)) {
184 auto createArrayRes = createArray.getResult();
185 const auto &elements = createArray.getElements();
186 if (elements.empty()) {
189 results.front()->setValue(
SourceRef(llvm::cast<OpResult>(createArrayRes)))
195 for (
auto [i, element] : llvm::enumerate(elements)) {
198 propagateIfChanged(results.front(), results.front()->setValue(newArrayVal));
202 if (
auto newPod = llvm::dyn_cast<NewPodOp>(op)) {
204 propagateIfChanged(results.front(), results.front()->setValue(newPodValue));
208 if (
auto structNewOp = llvm::dyn_cast<CreateStructOp>(op)) {
210 propagateIfChanged(results.front(), results.front()->setValue(newStructValue));
215 for (
Lattice *result : results) {
216 propagateIfChanged(result, updated);
222 CallOpInterface call, ArrayRef<const Lattice *> operandLattices,
223 ArrayRef<Lattice *> resultLattices
225 auto callable = dyn_cast_if_present<CallableOpInterface>(call.resolveCallable());
226 if (!callable || !callable.getCallableRegion()) {
228 for (
auto [result, lattice] : llvm::zip(call->getResults(), resultLattices)) {
230 ensure(succeeded(resultRef),
"could not create external call SourceRef");
231 propagateIfChanged(lattice, lattice->setValue(*resultRef));
235 if (resultLattices.empty()) {
244 ensure(succeeded(funcOpRes),
"could not lookup called function");
245 auto funcOp = funcOpRes->get();
247 const auto *predecessors = getOrCreateFor<mlir::dataflow::PredecessorState>(
248 getProgramPointAfter(call), getProgramPointAfter(call)
252 if (!predecessors->allPredecessorsKnown()) {
256 const auto returnSites = predecessors->getKnownPredecessors();
258 std::unordered_map<SourceRef, SourceRefLatticeValue, SourceRef::Hash> translation;
259 for (
auto [arg, lattice] : llvm::zip_equal(funcOp.getArguments(), operandLattices)) {
260 translation[
SourceRef(arg)] = lattice->getValue();
263 for (
auto [result, resultLattice] : llvm::zip(call->getResults(), resultLattices)) {
265 unsigned resultNum = llvm::cast<OpResult>(result).getResultNumber();
266 for (Operation *returnSite : returnSites) {
268 getProgramPointAfter(call.getOperation()),
269 returnSite->getOperand(resultNum)
272 auto [translatedVal, _] = retVal.translate(translation);
273 (void)combined.
update(translatedVal);
275 propagateIfChanged(resultLattice, resultLattice->setValue(combined));
280 Operation *op,
const OperandValues &operandVals, ArrayRef<Lattice *> results
282 auto updated = ChangeResult::NoChange;
283 for (
auto [res, lattice] : llvm::zip(op->getResults(), results)) {
285 for (
const auto &[_, opVal] : operandVals) {
286 (void)cur.update(opVal->getValue());
288 updated |= lattice->setValue(cur);
297 auto it = operandVals.find(
array);
298 ensure(it != operandVals.end(),
"improperly constructed operandVals map");
299 const auto &currVals = it->second->getValue();
301 std::vector<SourceRefIndex> indices;
302 for (
auto [i, idxOperand] : llvm::enumerate(arrayAccessOp.
getIndices())) {
303 auto idxIt = operandVals.find(idxOperand);
304 ensure(idxIt != operandVals.end(),
"improperly constructed operandVals map");
305 const auto &idxVals = idxIt->second->getValue();
307 auto constantIndex = idxVals.getConstantValue();
308 if (succeeded(constantIndex)) {
309 indices.emplace_back(*constantIndex);
311 auto arrayType = llvm::dyn_cast<ArrayType>(
array.getType());
312 assert(i <= std::numeric_limits<unsigned>::max() &&
"index too large");
319 auto newValsRes = currVals.extract(indices);
320 ensure(succeeded(newValsRes),
"could not create SourceRef child for array access");
321 auto [newVals, _] = *newValsRes;
322 if (llvm::isa<ReadArrayOp, WriteArrayOp>(arrayAccessOp)) {
323 ensure(newVals.isScalar(),
"array read/write must produce a scalar value");
331 ModuleOp m,
StructDefOp s, DataFlowSolver &solver, AnalysisManager &am,
335 if (cdg.computeConstraints(solver, am).failed()) {
336 return mlir::failure();
348 std::set<std::set<SourceRef>> sortedSets;
349 for (
const auto *entry : signalSets) {
350 if (!entry->isLeader()) {
354 auto members = signalSets.members(*entry);
355 std::set<SourceRef> sortedMembers(members.begin(), members.end());
359 if (sortedMembers.size() > 1) {
360 sortedSets.insert(sortedMembers);
364 for (
const auto &[ref, constSet] : constantSets) {
365 if (constSet.empty()) {
368 std::set<SourceRef> sortedMembers(constSet.begin(), constSet.end());
369 sortedMembers.insert(ref);
370 sortedSets.insert(sortedMembers);
373 os <<
"ConstraintDependencyGraph { ";
375 llvm::interleave(sortedSets, os, [&](
const auto &members) {
377 llvm::interleaveComma(members, os);
380 if (!sortedSets.empty()) {
387mlir::LogicalResult ConstraintDependencyGraph::computeConstraints(
388 mlir::DataFlowSolver &solver, mlir::AnalysisManager &am
394 "malformed struct " + mlir::Twine(structDef.getName()) +
" must define a constrain function"
405 constrainFnOp.walk([
this, &solver](Operation *op) {
410 for (Value operand : op->getOperands()) {
412 for (
const SourceRef &ref : operandRefs) {
413 ref2Val[ref].insert(operand);
416 for (Value result : op->getResults()) {
418 for (
const SourceRef &ref : resultRefs) {
419 ref2Val[ref].insert(result);
423 if (succeeded(writeTargetState)) {
424 for (
const SourceRef &ref : writeTargetState->foldToScalar()) {
425 ref2Val[ref].insert(op);
428 if (isa<EmitEqualityOp, EmitContainmentOp>(op)) {
429 this->walkConstrainOp(solver, op);
440 auto fnCallWalker = [
this, &solver, &am](CallOp fnCall)
mutable {
445 ensure(mlir::succeeded(res),
"could not resolve constrain call");
447 auto fn = res->get();
448 if (!fn.isStructConstrain()) {
452 auto calledStruct = fn.getOperation()->getParentOfType<StructDefOp>();
456 for (
auto [arg, operand] : llvm::zip_equal(fn.getArguments(), fnCall.getArgOperands())) {
459 auto &childAnalysis =
460 am.getChildAnalysis<ConstraintDependencyGraphStructAnalysis>(calledStruct);
461 if (!childAnalysis.constructed(ctx)) {
463 mlir::succeeded(childAnalysis.runAnalysis(solver, am, {.runIntraprocedural = false})),
464 "could not construct CDG for child struct"
467 auto translatedCDG = childAnalysis.getResult(ctx).translate(translations);
469 const auto &translatedRef2Val = translatedCDG.getRef2Val();
470 ref2Val.insert(translatedRef2Val.begin(), translatedRef2Val.end());
474 auto &tSets = translatedCDG.signalSets;
475 for (
const auto *entry : tSets) {
476 if (!entry->isLeader()) {
479 auto leader = entry->getData();
480 for (
const SourceRef &member : tSets.members(*entry)) {
481 signalSets.unionSets(leader, member);
485 for (
auto &[ref, constSet] : translatedCDG.constantSets) {
486 constantSets[ref].insert(constSet.begin(), constSet.end());
489 if (!ctx.runIntraproceduralAnalysis()) {
490 constrainFnOp.walk(fnCallWalker);
493 return mlir::success();
496void ConstraintDependencyGraph::walkConstrainOp(
497 mlir::DataFlowSolver &solver, mlir::Operation *emitOp
499 std::vector<SourceRef> signalUsages, constUsages;
501 for (
auto operand : emitOp->getOperands()) {
503 for (
const auto &ref : latticeVal.foldToScalar()) {
504 if (ref.isConstant()) {
505 constUsages.push_back(ref);
507 signalUsages.push_back(ref);
513 if (!signalUsages.empty()) {
514 auto leader = signalSets.getOrInsertLeaderValue(signalUsages.front());
515 for (
const SourceRef &signal : llvm::drop_begin(signalUsages)) {
516 signalSets.unionSets(leader, signal);
520 for (
auto &sig : signalUsages) {
521 constantSets[sig].insert(constUsages.begin(), constUsages.end());
529 [&translation](
const SourceRef &elem) -> mlir::FailureOr<std::vector<SourceRef>> {
530 std::vector<SourceRef> refs;
531 for (
auto &[prefix, vals] : translation) {
532 if (!elem.isValidPrefix(prefix)) {
536 if (vals.isArray()) {
538 auto suffix = elem.getSuffix(prefix);
540 mlir::succeeded(suffix),
"failure is nonsensical, we already checked for valid prefix"
543 auto resolvedValsRes = vals.extract(suffix.value());
544 ensure(succeeded(resolvedValsRes),
"could not create SourceRef child while resolving refs");
545 auto [resolvedVals, _] = *resolvedValsRes;
546 auto folded = resolvedVals.foldToScalar();
547 refs.insert(refs.end(), folded.begin(), folded.end());
549 for (
const auto &replacement : vals.getScalarValue()) {
550 auto translated = elem.translate(prefix, replacement);
551 if (mlir::succeeded(translated)) {
552 refs.push_back(translated.value());
558 return mlir::failure();
563 for (
const auto *entry : signalSets) {
564 if (!entry->isLeader()) {
568 std::vector<SourceRef> translatedSignals, translatedConsts;
569 for (
const SourceRef &member : signalSets.members(*entry)) {
571 if (mlir::failed(translated)) {
574 for (
const auto &ref : *translated) {
575 if (ref.isConstant()) {
576 translatedConsts.push_back(ref);
578 translatedSignals.push_back(ref);
582 if (
auto it = constantSets.find(member); it != constantSets.end()) {
583 const auto &origConstSet = it->second;
584 translatedConsts.insert(translatedConsts.end(), origConstSet.begin(), origConstSet.end());
588 if (translatedSignals.empty()) {
593 auto leader = translatedSignals.front();
594 res.signalSets.insert(leader);
595 for (
const SourceRef &signal : llvm::drop_begin(translatedSignals)) {
596 res.signalSets.insert(signal);
597 res.signalSets.unionSets(leader, signal);
601 for (
auto &ref : translatedSignals) {
602 res.constantSets[ref].insert(translatedConsts.begin(), translatedConsts.end());
607 for (
const auto &[ref, vals] : ref2Val) {
609 if (succeeded(translationRes)) {
610 for (
const auto &translatedRef : *translationRes) {
611 res.ref2Val[translatedRef].insert(vals.begin(), vals.end());
621 auto currRef = mlir::FailureOr<SourceRef>(ref);
622 while (mlir::succeeded(currRef)) {
625 for (
const auto *candidate : signalSets) {
626 const SourceRef &candidateRef = candidate->getData();
627 if (!candidateRef.
overlaps(*currRef)) {
630 for (
const SourceRef &member : signalSets.members(candidateRef)) {
631 if (!member.overlaps(ref)) {
635 auto constIt = constantSets.find(candidateRef);
636 if (constIt != constantSets.end()) {
637 res.insert(constIt->second.begin(), constIt->second.end());
641 currRef = currRef->getParentPrefix();
649 mlir::DataFlowSolver &solver, mlir::AnalysisManager &moduleAnalysisManager,
655 if (mlir::failed(result)) {
656 return mlir::failure();
659 return mlir::success();
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
Propagate references across a call treated as external.
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 through an operation.
static const Lattice * getLattice(mlir::DataFlowSolver &solver, mlir::Value val)
mlir::DenseMap< mlir::Value, const Lattice * > OperandValues
static SourceRefIndex forArrayDimension(int64_t size)
Select every nonnegative index in a static or dynamic array dimension.
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.
bool overlaps(const SourceRef &rhs) const
Return true when both references select overlapping storage at the same path depth.
component::StructDefOp getStruct() const
void setResult(const CDGAnalysisContext &ctx, ConstraintDependencyGraph &&r)
mlir::ModuleOp getModule() const
::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,...
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.