21#include <mlir/Analysis/DataFlow/DeadCodeAnalysis.h>
22#include <mlir/Analysis/DataFlow/DenseAnalysis.h>
23#include <mlir/IR/Value.h>
25#include <llvm/Support/Debug.h>
28#include <unordered_set>
30#define DEBUG_TYPE "llzk-cdg"
37using namespace component;
38using namespace constrain;
39using namespace function;
45 return solver.lookupState<
Lattice>(val);
49 if (
const auto *state =
getLattice(solver, val)) {
50 return state->getValue();
55mlir::FailureOr<SourceRefLatticeValue>
57 llvm::SmallDenseMap<Value, SourceRefLatticeValue, 4> operandVals;
58 for (Value operand : op->getOperands()) {
62 SymbolTableCollection tables;
63 if (
auto memberRefOp = llvm::dyn_cast<MemberRefOpInterface>(op)) {
64 if (!memberRefOp.isRead()) {
65 auto memberOpRes = memberRefOp.getMemberDefOp(tables);
66 ensure(succeeded(memberOpRes),
"could not find member write");
67 auto componentIt = operandVals.find(memberRefOp.getComponent());
68 ensure(componentIt != operandVals.end(),
"missing component lattice for member write");
69 auto memberValsRes = componentIt->second.referenceMember(memberOpRes.value());
70 ensure(succeeded(memberValsRes),
"could not create SourceRef child for member write");
71 return memberValsRes->first;
75 if (
auto podAccessOp = llvm::dyn_cast<PodAccessOpInterface>(op)) {
76 if (!podAccessOp.isRead()) {
77 auto podIt = operandVals.find(podAccessOp.getPodRef());
78 ensure(podIt != operandVals.end(),
"missing pod lattice for pod write");
79 auto podValsRes = podIt->second.referencePodRecord(podAccessOp.getRecordNameAttr());
80 ensure(succeeded(podValsRes),
"could not create SourceRef child for pod write");
81 return podValsRes->first;
85 if (
auto arrayAccessOp = llvm::dyn_cast<ArrayAccessOpInterface>(op)) {
86 if (llvm::isa<WriteArrayOp, InsertArrayOp>(arrayAccessOp)) {
87 auto array = arrayAccessOp.getArrRef();
88 auto it = operandVals.find(
array);
89 ensure(it != operandVals.end(),
"improperly constructed operandVals map");
90 const auto &currVals = it->second;
92 std::vector<SourceRefIndex> indices;
93 for (
size_t i = 0; i < arrayAccessOp.getIndices().size(); ++i) {
94 auto idxOperand = arrayAccessOp.getIndices()[i];
95 auto idxIt = operandVals.find(idxOperand);
96 ensure(idxIt != operandVals.end(),
"improperly constructed operandVals map");
97 const auto &idxVals = idxIt->second;
99 if (idxVals.isSingleValue() && idxVals.getSingleValue().isConstant()) {
100 indices.emplace_back(*idxVals.getSingleValue().getConstantValue());
102 auto arrayType = llvm::dyn_cast<ArrayType>(
array.getType());
103 auto lower = APInt::getZero(64);
104 assert(i <= std::numeric_limits<unsigned>::max() &&
"index too large");
105 APInt upper(64, arrayType.getDimSize(
static_cast<unsigned>(i)));
106 indices.emplace_back(lower, upper);
110 auto newValsRes = currVals.extract(indices);
111 ensure(succeeded(newValsRes),
"could not create SourceRef child for array access");
112 auto [newVals, _] = *newValsRes;
113 if (llvm::isa<WriteArrayOp>(arrayAccessOp)) {
114 ensure(newVals.isScalar(),
"array write must produce a scalar value");
120 return mlir::failure();
124 if (
auto value = llvm::dyn_cast_if_present<Value>(lattice->getAnchor())) {
125 if (
auto arg = llvm::dyn_cast<BlockArgument>(value)) {
126 Operation *parent = arg.getOwner()->getParentOp();
127 if (parent && llvm::isa<RegionBranchOpInterface>(parent) &&
128 llvm::isa<ArrayType, StructType, PodType>(value.getType())) {
140 Operation *op, ArrayRef<const Lattice *> operands, ArrayRef<Lattice *> results
142 LLVM_DEBUG(llvm::dbgs() <<
"SourceRefAnalysis::visitOperation: " << *op <<
'\n');
144 DenseMap<Value, const Lattice *> operandVals;
145 for (
auto [operand, lattice] : llvm::zip(op->getOperands(), operands)) {
146 operandVals[operand] = lattice;
149 if (
auto memberRefOp = llvm::dyn_cast<MemberRefOpInterface>(op)) {
150 auto memberOpRes = memberRefOp.getMemberDefOp(tables);
151 ensure(succeeded(memberOpRes),
"could not find member read");
153 operandVals.at(memberRefOp.getComponent())->getValue().referenceMember(memberOpRes.value());
154 ensure(succeeded(memberValsRes),
"could not create SourceRef child for member reference");
155 if (memberRefOp.isRead()) {
156 auto [memberVals, _] = *memberValsRes;
157 propagateIfChanged(results.front(), results.front()->setValue(memberVals));
162 if (
auto podAccessOp = llvm::dyn_cast<PodAccessOpInterface>(op)) {
163 auto podValsRes = operandVals.at(podAccessOp.getPodRef())
165 .referencePodRecord(podAccessOp.getRecordNameAttr());
166 ensure(succeeded(podValsRes),
"could not create SourceRef child for pod reference");
167 if (podAccessOp.isRead()) {
168 auto [podVals, _] = *podValsRes;
169 propagateIfChanged(results.front(), results.front()->setValue(podVals));
174 if (
auto arrayAccessOp = llvm::dyn_cast<ArrayAccessOpInterface>(op)) {
175 if (!results.empty()) {
177 propagateIfChanged(results.front(), results.front()->setValue(newVals));
182 if (
auto createArray = llvm::dyn_cast<CreateArrayOp>(op)) {
183 auto createArrayRes = createArray.getResult();
184 const auto &elements = createArray.getElements();
185 if (elements.empty()) {
188 results.front()->setValue(
SourceRef(llvm::cast<OpResult>(createArrayRes)))
194 for (
size_t i = 0; i < elements.size(); i++) {
197 propagateIfChanged(results.front(), results.front()->setValue(newArrayVal));
201 if (
auto newPod = llvm::dyn_cast<NewPodOp>(op)) {
203 propagateIfChanged(results.front(), results.front()->setValue(newPodValue));
207 if (
auto structNewOp = llvm::dyn_cast<CreateStructOp>(op)) {
209 propagateIfChanged(results.front(), results.front()->setValue(newStructValue));
214 for (
Lattice *result : results) {
215 propagateIfChanged(result, updated);
221 CallOpInterface call, ArrayRef<const Lattice *> operandLattices,
222 ArrayRef<Lattice *> resultLattices
224 auto callable = dyn_cast_if_present<CallableOpInterface>(call.resolveCallable());
225 if (!callable || !callable.getCallableRegion()) {
227 for (
auto [result, lattice] : llvm::zip(call->getResults(), resultLattices)) {
229 ensure(succeeded(resultRef),
"could not create external call SourceRef");
230 propagateIfChanged(lattice, lattice->setValue(*resultRef));
234 if (resultLattices.empty()) {
243 ensure(succeeded(funcOpRes),
"could not lookup called function");
244 auto funcOp = funcOpRes->get();
246 const auto *predecessors = getOrCreateFor<mlir::dataflow::PredecessorState>(
247 getProgramPointAfter(call), getProgramPointAfter(call)
251 if (!predecessors->allPredecessorsKnown()) {
255 const auto returnSites = predecessors->getKnownPredecessors();
257 std::unordered_map<SourceRef, SourceRefLatticeValue, SourceRef::Hash> translation;
258 for (
unsigned i = 0; i < funcOp.getNumArguments(); i++) {
259 translation[
SourceRef(funcOp.getArgument(i))] =
260 static_cast<const Lattice *
>(operandLattices[i])->getValue();
263 for (
auto [result, resultLattice] : llvm::zip(call->getResults(), resultLattices)) {
266 unsigned resultNum = llvm::cast<OpResult>(result).getResultNumber();
267 for (Operation *returnSite : returnSites) {
269 getProgramPointAfter(call.getOperation()),
270 returnSite->getOperand(resultNum)
273 auto [translatedVal, _] = retVal.translate(translation);
274 (void)combined.
update(translatedVal);
276 propagateIfChanged(resultLattice,
static_cast<Lattice *
>(resultLattice)->setValue(combined));
281 Operation *op,
const OperandValues &operandVals, ArrayRef<Lattice *> results
283 auto updated = ChangeResult::NoChange;
284 for (
auto [res, lattice] : llvm::zip(op->getResults(), results)) {
286 for (
const auto &[_, opVal] : operandVals) {
287 (void)cur.update(opVal->getValue());
289 updated |= lattice->setValue(cur);
298 auto it = operandVals.find(
array);
299 ensure(it != operandVals.end(),
"improperly constructed operandVals map");
300 const auto &currVals = it->second->getValue();
302 std::vector<SourceRefIndex> indices;
303 for (
size_t i = 0; i < arrayAccessOp.
getIndices().size(); ++i) {
304 auto idxOperand = arrayAccessOp.
getIndices()[i];
305 auto idxIt = operandVals.find(idxOperand);
306 ensure(idxIt != operandVals.end(),
"improperly constructed operandVals map");
307 const auto &idxVals = idxIt->second->getValue();
309 if (idxVals.isSingleValue() && idxVals.getSingleValue().isConstant()) {
310 indices.emplace_back(*idxVals.getSingleValue().getConstantValue());
312 auto arrayType = llvm::dyn_cast<ArrayType>(
array.getType());
313 auto lower = APInt::getZero(64);
314 assert(i <= std::numeric_limits<unsigned>::max() &&
"index too large");
315 APInt upper(64, arrayType.getDimSize(
static_cast<unsigned>(i)));
316 indices.emplace_back(lower, upper);
320 auto newValsRes = currVals.extract(indices);
321 ensure(succeeded(newValsRes),
"could not create SourceRef child for array access");
322 auto [newVals, _] = *newValsRes;
323 if (llvm::isa<ReadArrayOp, WriteArrayOp>(arrayAccessOp)) {
324 ensure(newVals.isScalar(),
"array read/write must produce a scalar value");
332 ModuleOp m,
StructDefOp s, DataFlowSolver &solver, AnalysisManager &am,
336 if (cdg.computeConstraints(solver, am).failed()) {
337 return mlir::failure();
349 std::set<std::set<SourceRef>> sortedSets;
350 for (
auto it = signalSets.begin(); it != signalSets.end(); it++) {
351 if (!it->isLeader()) {
355 std::set<SourceRef> sortedMembers;
356 for (
auto mit = signalSets.member_begin(it); mit != signalSets.member_end(); mit++) {
357 sortedMembers.insert(*mit);
362 if (sortedMembers.size() > 1) {
363 sortedSets.insert(sortedMembers);
367 for (
const auto &[ref, constSet] : constantSets) {
368 if (constSet.empty()) {
371 std::set<SourceRef> sortedMembers(constSet.begin(), constSet.end());
372 sortedMembers.insert(ref);
373 sortedSets.insert(sortedMembers);
376 os <<
"ConstraintDependencyGraph { ";
378 for (
auto it = sortedSets.begin(); it != sortedSets.end();) {
380 for (
auto mit = it->begin(); mit != it->end();) {
383 if (mit != it->end()) {
389 if (it == sortedSets.end()) {
399mlir::LogicalResult ConstraintDependencyGraph::computeConstraints(
400 mlir::DataFlowSolver &solver, mlir::AnalysisManager &am
406 "malformed struct " + mlir::Twine(structDef.getName()) +
" must define a constrain function"
417 constrainFnOp.walk([
this, &solver](Operation *op) {
422 for (Value operand : op->getOperands()) {
424 for (
const SourceRef &ref : operandRefs) {
425 ref2Val[ref].insert(operand);
428 for (Value result : op->getResults()) {
430 for (
const SourceRef &ref : resultRefs) {
431 ref2Val[ref].insert(result);
435 if (succeeded(writeTargetState)) {
436 for (
const SourceRef &ref : writeTargetState->foldToScalar()) {
437 ref2Val[ref].insert(op);
440 if (isa<EmitEqualityOp, EmitContainmentOp>(op)) {
441 this->walkConstrainOp(solver, op);
452 auto fnCallWalker = [
this, &solver, &am](CallOp fnCall)
mutable {
457 ensure(mlir::succeeded(res),
"could not resolve constrain call");
459 auto fn = res->get();
460 if (!fn.isStructConstrain()) {
464 auto calledStruct = fn.getOperation()->getParentOfType<StructDefOp>();
468 for (
unsigned i = 0; i < fn.getNumArguments(); i++) {
469 SourceRef prefix(fn.getArgument(i));
470 Value operand = fnCall.getOperand(i);
472 translations.push_back({prefix, val});
474 auto &childAnalysis =
475 am.getChildAnalysis<ConstraintDependencyGraphStructAnalysis>(calledStruct);
476 if (!childAnalysis.constructed(ctx)) {
478 mlir::succeeded(childAnalysis.runAnalysis(solver, am, {.runIntraprocedural = false})),
479 "could not construct CDG for child struct"
482 auto translatedCDG = childAnalysis.getResult(ctx).translate(translations);
484 const auto &translatedRef2Val = translatedCDG.getRef2Val();
485 ref2Val.insert(translatedRef2Val.begin(), translatedRef2Val.end());
489 auto &tSets = translatedCDG.signalSets;
490 for (
auto lit = tSets.begin(); lit != tSets.end(); lit++) {
491 if (!lit->isLeader()) {
494 auto leader = lit->getData();
495 for (
auto mit = tSets.member_begin(lit); mit != tSets.member_end(); mit++) {
496 signalSets.unionSets(leader, *mit);
500 for (
auto &[ref, constSet] : translatedCDG.constantSets) {
501 constantSets[ref].insert(constSet.begin(), constSet.end());
504 if (!ctx.runIntraproceduralAnalysis()) {
505 constrainFnOp.walk(fnCallWalker);
508 return mlir::success();
511void ConstraintDependencyGraph::walkConstrainOp(
512 mlir::DataFlowSolver &solver, mlir::Operation *emitOp
514 std::vector<SourceRef> signalUsages, constUsages;
516 for (
auto operand : emitOp->getOperands()) {
518 for (
const auto &ref : latticeVal.foldToScalar()) {
519 if (ref.isConstant()) {
520 constUsages.push_back(ref);
522 signalUsages.push_back(ref);
528 if (!signalUsages.empty()) {
529 auto it = signalUsages.begin();
530 auto leader = signalSets.getOrInsertLeaderValue(*it);
531 for (it++; it != signalUsages.end(); it++) {
532 signalSets.unionSets(leader, *it);
536 for (
auto &sig : signalUsages) {
537 constantSets[sig].insert(constUsages.begin(), constUsages.end());
545 [&translation](
const SourceRef &elem) -> mlir::FailureOr<std::vector<SourceRef>> {
546 std::vector<SourceRef> refs;
547 for (
auto &[prefix, vals] : translation) {
548 if (!elem.isValidPrefix(prefix)) {
552 if (vals.isArray()) {
554 auto suffix = elem.getSuffix(prefix);
556 mlir::succeeded(suffix),
"failure is nonsensical, we already checked for valid prefix"
559 auto resolvedValsRes = vals.extract(suffix.value());
560 ensure(succeeded(resolvedValsRes),
"could not create SourceRef child while resolving refs");
561 auto [resolvedVals, _] = *resolvedValsRes;
562 auto folded = resolvedVals.foldToScalar();
563 refs.insert(refs.end(), folded.begin(), folded.end());
565 for (
const auto &replacement : vals.getScalarValue()) {
566 auto translated = elem.translate(prefix, replacement);
567 if (mlir::succeeded(translated)) {
568 refs.push_back(translated.value());
574 return mlir::failure();
579 for (
auto leaderIt = signalSets.begin(); leaderIt != signalSets.end(); leaderIt++) {
580 if (!leaderIt->isLeader()) {
584 std::vector<SourceRef> translatedSignals, translatedConsts;
585 for (
auto mit = signalSets.member_begin(leaderIt); mit != signalSets.member_end(); mit++) {
587 if (mlir::failed(member)) {
590 for (
const auto &ref : *member) {
591 if (ref.isConstant()) {
592 translatedConsts.push_back(ref);
594 translatedSignals.push_back(ref);
598 if (
auto it = constantSets.find(*mit); it != constantSets.end()) {
599 const auto &origConstSet = it->second;
600 translatedConsts.insert(translatedConsts.end(), origConstSet.begin(), origConstSet.end());
604 if (translatedSignals.empty()) {
609 auto it = translatedSignals.begin();
611 res.signalSets.insert(leader);
612 for (it++; it != translatedSignals.end(); it++) {
613 res.signalSets.insert(*it);
614 res.signalSets.unionSets(leader, *it);
618 for (
auto &ref : translatedSignals) {
619 res.constantSets[ref].insert(translatedConsts.begin(), translatedConsts.end());
624 for (
const auto &[ref, vals] : ref2Val) {
626 if (succeeded(translationRes)) {
627 for (
const auto &translatedRef : *translationRes) {
628 res.ref2Val[translatedRef].insert(vals.begin(), vals.end());
638 auto currRef = mlir::FailureOr<SourceRef>(ref);
639 while (mlir::succeeded(currRef)) {
642 for (
auto candidate = signalSets.begin(); candidate != signalSets.end(); ++candidate) {
643 const SourceRef &candidateRef = candidate->getData();
644 if (!candidateRef.
overlaps(*currRef)) {
647 for (
auto it = signalSets.findLeader(candidate); it != signalSets.member_end(); ++it) {
648 if (!it->overlaps(ref)) {
652 auto constIt = constantSets.find(candidateRef);
653 if (constIt != constantSets.end()) {
654 res.insert(constIt->second.begin(), constIt->second.end());
658 currRef = currRef->getParentPrefix();
666 mlir::DataFlowSolver &solver, mlir::AnalysisManager &moduleAnalysisManager,
672 if (mlir::failed(result)) {
673 return mlir::failure();
676 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
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.
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.