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 "llzk/Util/Walk.h"
21
22#include <mlir/Analysis/DataLayoutAnalysis.h>
23#include <mlir/Dialect/SCF/IR/SCF.h>
24#include <mlir/IR/Builders.h>
25#include <mlir/IR/Dominance.h>
26#include <mlir/Interfaces/MemorySlotInterfaces.h>
27#include <mlir/Pass/Pass.h>
28#include <mlir/Pass/PassManager.h>
29#include <mlir/Transforms/Mem2Reg.h>
30#include <mlir/Transforms/Passes.h>
31#include <mlir/Transforms/SROA.h>
32
33#include <llvm/ADT/SmallVector.h>
34
35namespace llzk {
36
40template <typename AllocOpTy>
41struct SpecializedSROA : mlir::PassWrapper<SpecializedSROA<AllocOpTy>, mlir::OperationPass<>> {
42
43 mlir::StringRef getArgument() const override { return "llzk-specialized-sroa"; }
44
45 mlir::StringRef getDescription() const override {
46 return "Scalar replacement of aggregates for a specific allocator op type";
47 }
48
49 void runOnOperation() override {
50 mlir::Operation *scopeOp = this->getOperation();
51
52 auto &dataLayoutAnalysis = this->template getAnalysis<mlir::DataLayoutAnalysis>();
53 const mlir::DataLayout &dataLayout = dataLayoutAnalysis.getAtOrAbove(scopeOp);
54
55 bool changed = false;
56
57 for (mlir::Region &region : scopeOp->getRegions()) {
58 if (region.getBlocks().empty()) {
59 continue;
60 }
61
62 mlir::OpBuilder builder(&region.front(), region.front().begin());
63
64 auto allocators = walkCollectMapped<AllocOpTy>(region, [](auto allocator) {
65 return mlir::DestructurableAllocationOpInterface(allocator);
66 });
67
68 if (mlir::succeeded(mlir::tryToDestructureMemorySlots(allocators, builder, dataLayout))) {
69 changed = true;
70 }
71 }
72
73 if (!changed) {
74 this->markAllAnalysesPreserved();
75 }
76 }
77};
78
79// Pass factory for `SpecializedSROA`.
80template <typename AllocOpTy>
81std::unique_ptr<SpecializedSROA<AllocOpTy>> createSpecializedSROAPass() {
82 return std::make_unique<SpecializedSROA<AllocOpTy>>();
83}
84
88template <typename AllocOpTy>
90 : mlir::PassWrapper<SpecializedMem2Reg<AllocOpTy>, mlir::OperationPass<>> {
91
92 mlir::StringRef getArgument() const override { return "llzk-specialized-mem2reg"; }
93
94 mlir::StringRef getDescription() const override {
95 return "Promotes memory slots of a specific allocator op type into values";
96 }
97
98 void runOnOperation() override {
99 mlir::Operation *scopeOp = this->getOperation();
100
101 auto &dataLayoutAnalysis = this->template getAnalysis<mlir::DataLayoutAnalysis>();
102 const mlir::DataLayout &dataLayout = dataLayoutAnalysis.getAtOrAbove(scopeOp);
103 auto &dominance = this->template getAnalysis<mlir::DominanceInfo>();
104
105 bool changed = false;
106
107 for (mlir::Region &region : scopeOp->getRegions()) {
108 if (region.getBlocks().empty()) {
109 continue;
110 }
111
112 mlir::OpBuilder builder(&region.front(), region.front().begin());
113
114 auto allocators = walkCollectMapped<AllocOpTy>(region, [](auto allocator) {
115 return mlir::PromotableAllocationOpInterface(allocator);
116 });
117
118 auto promoteRes = mlir::tryToPromoteMemorySlots(allocators, builder, dataLayout, dominance);
119 if (mlir::succeeded(promoteRes)) {
120 changed = true;
121 }
122 }
123
124 if (!changed) {
125 this->markAllAnalysesPreserved();
126 }
127 }
128};
129
130// Pass factory for `SpecializedMem2Reg`.
131template <typename AllocOpTy>
132std::unique_ptr<SpecializedMem2Reg<AllocOpTy>> createSpecializedMem2RegPass() {
133 return std::make_unique<SpecializedMem2Reg<AllocOpTy>>();
134}
135
136namespace detail {
137
142 : public mlir::PassWrapper<RemoveDeadValuesWorkaroundPass, mlir::OperationPass<>> {
143public:
144 MLIR_DEFINE_EXPLICIT_INTERNAL_INLINE_TYPE_ID(RemoveDeadValuesWorkaroundPass)
145
146 llvm::StringRef getArgument() const override { return "remove-dead-values"; }
147 llvm::StringRef getDescription() const override { return "Remove dead values"; }
148
149 void runOnOperation() final {
150 mlir::Operation *scopeOp = this->getOperation();
151
152 // Pre-pass: add a trivial block to empty `else` regions so upstream pass code can handle them.
153 scopeOp->walk([](mlir::scf::IfOp ifOp) {
154 if (ifOp.getElseRegion().empty()) {
155 mlir::Block &elseBlock = ifOp.getElseRegion().emplaceBlock();
156 mlir::OpBuilder builder(ifOp.getContext());
157 builder.setInsertionPointToEnd(&elseBlock);
158 builder.create<mlir::scf::YieldOp>(ifOp.getLoc());
159 }
160 });
161
162 mlir::OpPassManager pm(scopeOp->getName().getStringRef());
163 pm.addPass(mlir::createRemoveDeadValuesPass());
164 if (mlir::failed(runPipeline(pm, scopeOp))) {
165 signalPassFailure();
166 }
167
168 // Post-pass: remove trivial `else` blocks that are left behind.
169 scopeOp->walk([](mlir::scf::IfOp ifOp) {
170 if (ifOp.getResults().empty()) {
171 mlir::Region &elseRegion = ifOp.getElseRegion();
172 if (!llvm::hasSingleElement(elseRegion)) {
173 return;
174 }
175 mlir::Block &elseBlock = elseRegion.front();
176 if (!llvm::hasSingleElement(elseBlock)) {
177 return;
178 }
179 if (!llvm::isa<mlir::scf::YieldOp>(elseBlock.front())) {
180 return;
181 }
182 elseRegion.dropAllReferences();
183 elseBlock.clear();
184 elseRegion.getBlocks().clear();
185 }
186 });
187 }
188};
189
190} // namespace detail
191
192inline std::unique_ptr<mlir::Pass> createRemoveDeadValuesWorkaroundPass() {
193 return std::make_unique<detail::RemoveDeadValuesWorkaroundPass>();
194}
195
196} // 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