LLZK 3.0.0
An open-source IR for Zero Knowledge (ZK) circuits
Loading...
Searching...
No Matches
SpecializedMemoryPasses.h
Go to the documentation of this file.
1//===-- SpecializedMemoryPasses.h - Targeted SROA / mem2reg -----*- C++ -*-===//
2//
3// Part of the LLZK Project, under the Apache License v2.0.
4// See LICENSE.txt for license information.
5// Copyright 2026 Project LLZK
6// SPDX-License-Identifier: Apache-2.0
7//
8//===----------------------------------------------------------------------===//
16//===----------------------------------------------------------------------===//
17
18#pragma once
19
20#include <mlir/Analysis/DataLayoutAnalysis.h>
21#include <mlir/Dialect/SCF/IR/SCF.h>
22#include <mlir/IR/Builders.h>
23#include <mlir/IR/Dominance.h>
24#include <mlir/Interfaces/MemorySlotInterfaces.h>
25#include <mlir/Pass/Pass.h>
26#include <mlir/Pass/PassManager.h>
27#include <mlir/Transforms/Mem2Reg.h>
28#include <mlir/Transforms/Passes.h>
29#include <mlir/Transforms/SROA.h>
30
31#include <llvm/ADT/SmallVector.h>
32
33namespace llzk {
34
38template <typename AllocOpTy>
39struct SpecializedSROA : mlir::PassWrapper<SpecializedSROA<AllocOpTy>, mlir::OperationPass<>> {
40
41 mlir::StringRef getArgument() const override { return "llzk-specialized-sroa"; }
42
43 mlir::StringRef getDescription() const override {
44 return "Scalar replacement of aggregates for a specific allocator op type";
45 }
46
47 void runOnOperation() override {
48 mlir::Operation *scopeOp = this->getOperation();
49
50 auto &dataLayoutAnalysis = this->template getAnalysis<mlir::DataLayoutAnalysis>();
51 const mlir::DataLayout &dataLayout = dataLayoutAnalysis.getAtOrAbove(scopeOp);
52
53 bool changed = false;
54
55 for (mlir::Region &region : scopeOp->getRegions()) {
56 if (region.getBlocks().empty()) {
57 continue;
58 }
59
60 mlir::OpBuilder builder(&region.front(), region.front().begin());
61
62 mlir::SmallVector<mlir::DestructurableAllocationOpInterface> allocators;
63 region.walk([&](AllocOpTy allocator) { allocators.emplace_back(allocator); });
64
65 if (mlir::succeeded(mlir::tryToDestructureMemorySlots(allocators, builder, dataLayout))) {
66 changed = true;
67 }
68 }
69
70 if (!changed) {
71 this->markAllAnalysesPreserved();
72 }
73 }
74};
75
76// Pass factory for `SpecializedSROA`.
77template <typename AllocOpTy>
78std::unique_ptr<SpecializedSROA<AllocOpTy>> createSpecializedSROAPass() {
79 return std::make_unique<SpecializedSROA<AllocOpTy>>();
80}
81
85template <typename AllocOpTy>
87 : mlir::PassWrapper<SpecializedMem2Reg<AllocOpTy>, mlir::OperationPass<>> {
88
89 mlir::StringRef getArgument() const override { return "llzk-specialized-mem2reg"; }
90
91 mlir::StringRef getDescription() const override {
92 return "Promotes memory slots of a specific allocator op type into values";
93 }
94
95 void runOnOperation() override {
96 mlir::Operation *scopeOp = this->getOperation();
97
98 auto &dataLayoutAnalysis = this->template getAnalysis<mlir::DataLayoutAnalysis>();
99 const mlir::DataLayout &dataLayout = dataLayoutAnalysis.getAtOrAbove(scopeOp);
100 auto &dominance = this->template getAnalysis<mlir::DominanceInfo>();
101
102 bool changed = false;
103
104 for (mlir::Region &region : scopeOp->getRegions()) {
105 if (region.getBlocks().empty()) {
106 continue;
107 }
108
109 mlir::OpBuilder builder(&region.front(), region.front().begin());
110
111 mlir::SmallVector<mlir::PromotableAllocationOpInterface> allocators;
112 region.walk([&](AllocOpTy allocator) { allocators.emplace_back(allocator); });
113
114 if (mlir::succeeded(
115 mlir::tryToPromoteMemorySlots(allocators, builder, dataLayout, dominance)
116 )) {
117 changed = true;
118 }
119 }
120
121 if (!changed) {
122 this->markAllAnalysesPreserved();
123 }
124 }
125};
126
127// Pass factory for `SpecializedMem2Reg`.
128template <typename AllocOpTy>
129std::unique_ptr<SpecializedMem2Reg<AllocOpTy>> createSpecializedMem2RegPass() {
130 return std::make_unique<SpecializedMem2Reg<AllocOpTy>>();
131}
132
133namespace detail {
134
139 : public mlir::PassWrapper<RemoveDeadValuesWorkaroundPass, mlir::OperationPass<>> {
140public:
141 MLIR_DEFINE_EXPLICIT_INTERNAL_INLINE_TYPE_ID(RemoveDeadValuesWorkaroundPass)
142
143 llvm::StringRef getArgument() const override { return "remove-dead-values"; }
144 llvm::StringRef getDescription() const override { return "Remove dead values"; }
145
146 void runOnOperation() final {
147 // Pre-pass: add a trivial block to empty `else` regions so upstream pass code can handle them.
148 getOperation()->walk([](mlir::scf::IfOp ifOp) {
149 if (ifOp.getElseRegion().empty()) {
150 mlir::Block &elseBlock = ifOp.getElseRegion().emplaceBlock();
151 mlir::OpBuilder builder(ifOp.getContext());
152 builder.setInsertionPointToEnd(&elseBlock);
153 builder.create<mlir::scf::YieldOp>(ifOp.getLoc());
154 }
155 });
156
157 mlir::OpPassManager pm(getOperation()->getName().getStringRef());
158 pm.addPass(mlir::createRemoveDeadValuesPass());
159 if (mlir::failed(runPipeline(pm, getOperation()))) {
160 signalPassFailure();
161 }
162
163 // Post-pass: remove trivial `else` blocks that are left behind.
164 getOperation()->walk([](mlir::scf::IfOp ifOp) {
165 if (ifOp.getResults().empty()) {
166 mlir::Region &elseRegion = ifOp.getElseRegion();
167 if (!llvm::hasSingleElement(elseRegion)) {
168 return;
169 }
170 mlir::Block &elseBlock = elseRegion.front();
171 if (!llvm::hasSingleElement(elseBlock)) {
172 return;
173 }
174 if (!llvm::isa<mlir::scf::YieldOp>(elseBlock.front())) {
175 return;
176 }
177 elseRegion.dropAllReferences();
178 elseBlock.clear();
179 elseRegion.getBlocks().clear();
180 }
181 });
182 }
183};
184
185} // namespace detail
186
187inline std::unique_ptr<mlir::Pass> createRemoveDeadValuesWorkaroundPass() {
188 return std::make_unique<detail::RemoveDeadValuesWorkaroundPass>();
189}
190
191} // namespace llzk
A workaround wrapper around MLIR's remove-dead-values pass that normalizes empty scf....
std::unique_ptr< mlir::Pass > createRemoveDeadValuesWorkaroundPass()
std::unique_ptr< SpecializedMem2Reg< AllocOpTy > > createSpecializedMem2RegPass()
std::unique_ptr< SpecializedSROA< AllocOpTy > > createSpecializedSROAPass()
A variant of the MLIR mem2reg pass that only promotes memory slots belonging to allocators of type Al...
mlir::StringRef getArgument() const override
mlir::StringRef getDescription() const override
A variant of the MLIR sroa pass that only destructures memory slots belonging to allocators of type A...
mlir::StringRef getDescription() const override
mlir::StringRef getArgument() const override