LLZK 3.0.0
An open-source IR for Zero Knowledge (ZK) circuits
Loading...
Searching...
No Matches
SparseAnalysis.cpp
Go to the documentation of this file.
1//===- SparseAnalysis.cpp - LLZK sparse data-flow adapter -----------------===//
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// Copyright 2026 Project LLZK
7// SPDX-License-Identifier: Apache-2.0
8//
9//===----------------------------------------------------------------------===//
10
12
13#include <mlir/Analysis/DataFlow/DeadCodeAnalysis.h>
14#include <mlir/Analysis/DataFlowFramework.h>
15#include <mlir/IR/Block.h>
16#include <mlir/IR/Operation.h>
17#include <mlir/IR/Region.h>
18#include <mlir/IR/Value.h>
19#include <mlir/Interfaces/CallInterfaces.h>
20#include <mlir/Interfaces/ControlFlowInterfaces.h>
21#include <mlir/Support/LogicalResult.h>
22
23#include <llvm/ADT/SmallVector.h>
24#include <llvm/Support/Casting.h>
25
26using namespace mlir;
27
28namespace llzk::dataflow {
29
32
34 // Match upstream sparse initialization of top-level region entry arguments.
35 for (Region &region : top->getRegions()) {
36 if (region.empty()) {
37 continue;
38 }
39 for (Value argument : region.front().getArguments()) {
40 setToEntryState(getLatticeElement(argument));
41 }
42 }
43
44 return initializeRecursivelyInProgramOrder(top);
45}
46
47LogicalResult AbstractSparseForwardDataFlowAnalysis::visit(ProgramPoint *point) {
48 if (point->isBlockStart()) {
49 return ::mlir::dataflow::AbstractSparseForwardDataFlowAnalysis::visit(point);
50 }
51
52 Operation *op = point->getPrevOp();
53 if (op->getNumResults() != 0) {
54 return ::mlir::dataflow::AbstractSparseForwardDataFlowAnalysis::visit(point);
55 }
56 return visitZeroResultOperation(op);
57}
58
59LogicalResult
60AbstractSparseForwardDataFlowAnalysis::initializeRecursivelyInProgramOrder(Operation *op) {
61 if (failed(visitOperationDuringInitialization(op))) {
62 return failure();
63 }
64
65 for (Region &region : op->getRegions()) {
66 for (Block &block : region) {
67 ProgramPoint *blockStart = getProgramPointBefore(&block);
68 getOrCreate<::mlir::dataflow::Executable>(blockStart)->blockContentSubscribe(this);
69 if (failed(::mlir::dataflow::AbstractSparseForwardDataFlowAnalysis::visit(blockStart))) {
70 return failure();
71 }
72 for (Operation &nestedOp : block) {
73 if (failed(initializeRecursivelyInProgramOrder(&nestedOp))) {
74 return failure();
75 }
76 }
77 }
78 }
79
80 return success();
81}
82
83LogicalResult
84AbstractSparseForwardDataFlowAnalysis::visitOperationDuringInitialization(Operation *op) {
85 if (op->getNumResults() == 0) {
86 // Preserve LLZK's zero-result transfer behavior for live effect ops such as
87 // constraints, assertions, and writes.
88 return visitZeroResultOperation(op);
89 }
90 return ::mlir::dataflow::AbstractSparseForwardDataFlowAnalysis::visit(getProgramPointAfter(op));
91}
92
93bool AbstractSparseForwardDataFlowAnalysis::isOperationLive(Operation *op) {
94 if (op->getBlock() == nullptr) {
95 return true;
96 }
97 return getOrCreate<::mlir::dataflow::Executable>(getProgramPointBefore(op->getBlock()))->isLive();
98}
99
100llvm::SmallVector<const AbstractSparseLattice *, 4>
101AbstractSparseForwardDataFlowAnalysis::collectOperandLatticesAndSubscribe(Operation *op) {
102 llvm::SmallVector<const AbstractSparseLattice *, 4> operandLattices;
103 operandLattices.reserve(op->getNumOperands());
104 for (Value operand : op->getOperands()) {
105 AbstractSparseLattice *operandLattice = getLatticeElement(operand);
106 operandLattice->useDefSubscribe(this);
107 operandLattices.push_back(operandLattice);
108 }
109 return operandLattices;
110}
111
112LogicalResult AbstractSparseForwardDataFlowAnalysis::visitZeroResultCallOperation(
113 CallOpInterface call, ArrayRef<const AbstractSparseLattice *> operandLattices
114) {
115 ArrayRef<AbstractSparseLattice *> emptyResultLattices;
116
117 // Preserve the external-call hook. LLZK analyses may use it for no-result
118 // call side effects even when there are no result lattices to update.
119 auto callable = llvm::dyn_cast_if_present<CallableOpInterface>(call.resolveCallable());
120 if (!getSolverConfig().isInterprocedural() || (callable && !callable.getCallableRegion())) {
121 visitExternalCallImpl(call, operandLattices, emptyResultLattices);
122 return success();
123 }
124
125 // Internal zero-result calls have no result lattices, but keep a callgraph
126 // dependency so later predecessor updates can revisit the call site.
127 Operation *callOp = call.getOperation();
128 (void)getOrCreateFor<::mlir::dataflow::PredecessorState>(
129 getProgramPointAfter(callOp), getProgramPointAfter(callOp)
130 );
131 return success();
132}
133
134LogicalResult AbstractSparseForwardDataFlowAnalysis::visitZeroResultOperation(Operation *op) {
135 if (!isOperationLive(op)) {
136 return success();
137 }
138
139 // Region-branch operations are fully owned by upstream control-flow
140 // propagation. For zero-result region branches, there are no parent result
141 // lattices for this compatibility path to update.
142 if (llvm::isa<RegionBranchOpInterface>(op)) {
143 return success();
144 }
145
146 auto operandLattices = collectOperandLatticesAndSubscribe(op);
147
148 if (auto call = llvm::dyn_cast<CallOpInterface>(op)) {
149 return visitZeroResultCallOperation(call, operandLattices);
150 }
151
152 // Invoke the typed operation transfer function with an empty result range.
153 ArrayRef<AbstractSparseLattice *> emptyResultLattices;
154 return visitOperationImpl(op, operandLattices, emptyResultLattices);
155}
156
157} // namespace llzk::dataflow
This file provides LLZK's sparse forward data-flow analysis compatibility layer.
mlir::LogicalResult visit(mlir::ProgramPoint *point) override
Delegate block starts and result-producing operations to upstream MLIR.
mlir::LogicalResult initialize(mlir::Operation *top) override
Initialize the analysis while preserving the program-order visitation of the old LLZK sparse analysis...
mlir::dataflow::AbstractSparseLattice AbstractSparseLattice