LLZK 3.0.0
An open-source IR for Zero Knowledge (ZK) circuits
Loading...
Searching...
No Matches
ConversionUtils.h
Go to the documentation of this file.
1//===-- ConversionUtils.h ---------------------------------------*- C++ -*-===//
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// SPDX-License-Identifier: Apache-2.0
7//
8//===----------------------------------------------------------------------===//
9//
10// Shared utilities for dialect converting transformations.
11//
12//===----------------------------------------------------------------------===//
13
14#pragma once
15
19#include "llzk/Util/Concepts.h"
20
21#include <mlir/IR/Block.h>
22#include <mlir/IR/Builders.h>
23#include <mlir/IR/BuiltinAttributes.h>
24#include <mlir/IR/BuiltinTypes.h>
25#include <mlir/IR/Operation.h>
26#include <mlir/IR/PatternMatch.h>
27#include <mlir/IR/SymbolTable.h>
28#include <mlir/Transforms/DialectConversion.h>
29
30#include <llvm/ADT/ArrayRef.h>
31#include <llvm/ADT/DenseMap.h>
32#include <llvm/ADT/STLExtras.h>
33#include <llvm/ADT/SmallVector.h>
34#include <llvm/ADT/StringRef.h>
35#include <llvm/ADT/StringSet.h>
36#include <llvm/ADT/Twine.h>
37#include <llvm/Support/Casting.h>
38#include <llvm/Support/ErrorHandling.h>
39
40#include <cassert>
41#include <optional>
42#include <string>
43#include <tuple>
44#include <utility>
45
46namespace llzk {
47
49template <typename OpTy> inline OpTy preserveDiscardableAttrs(mlir::Operation *src, OpTy dst) {
50 dst->setDiscardableAttrs(src->getDiscardableAttrDictionary());
51 return dst;
52}
53
55template <typename OpTy>
56inline OpTy
57preserveDiscardableAttrsExcept(mlir::Operation *src, OpTy dst, mlir::StringRef excludedAttr) {
58 auto original = src->getDiscardableAttrDictionary();
59 mlir::SmallVector<mlir::NamedAttribute> attrs;
60 for (mlir::NamedAttribute attr : original.getValue()) {
61 if (attr.getName().getValue() != excludedAttr) {
62 attrs.push_back(attr);
63 }
64 }
65 dst->setDiscardableAttrs(mlir::DictionaryAttr::get(src->getContext(), attrs));
66 return dst;
67}
68
71template <typename OpClass, typename Rewriter, typename... Args>
72inline OpClass replaceOpWithNewOp(Rewriter &rewriter, mlir::Operation *op, Args &&...args) {
73 mlir::DictionaryAttr attrs = op->getDiscardableAttrDictionary();
74 OpClass newOp = rewriter.template replaceOpWithNewOp<OpClass>(op, std::forward<Args>(args)...);
75 newOp->setDiscardableAttrs(attrs);
76 return newOp;
77}
78
81inline mlir::DictionaryAttr
82withFunctionNameAttr(mlir::DictionaryAttr attrs, llvm::StringRef attrName, llvm::StringRef name) {
83 mlir::NamedAttrList newAttrs(attrs);
84 newAttrs.set(attrName, mlir::StringAttr::get(attrs.getContext(), name));
85 return newAttrs.getDictionary(attrs.getContext());
86}
87
90inline mlir::DictionaryAttr
91withFunctionArgNameAttr(mlir::DictionaryAttr attrs, llvm::StringRef name) {
93}
94
96inline mlir::DictionaryAttr
97withFunctionResNameAttr(mlir::DictionaryAttr attrs, llvm::StringRef name) {
99}
100
102inline std::string
103reserveUniqueAttrName(llvm::StringSet<> &usedNames, llvm::StringRef desiredName) {
104 if (!usedNames.contains(desiredName)) {
105 usedNames.insert(desiredName);
106 return desiredName.str();
107 }
108
109 for (unsigned suffix = 1;; ++suffix) {
110 std::string candidate = (desiredName + "#" + llvm::Twine(suffix)).str();
111 if (!usedNames.contains(candidate)) {
112 usedNames.insert(candidate);
113 return candidate;
114 }
115 }
116}
117
119inline std::optional<mlir::StringAttr>
120getAttrAtIndexWithName(mlir::ArrayAttr attrs, unsigned index, llvm::StringRef attrName) {
121 if (!attrs || index >= attrs.size()) {
122 return std::nullopt;
123 }
124 if (auto dictAttr = llvm::dyn_cast<mlir::DictionaryAttr>(attrs[index])) {
125 if (auto nameAttr = llvm::dyn_cast_if_present<mlir::StringAttr>(dictAttr.get(attrName))) {
126 return nameAttr;
127 }
128 }
129 return std::nullopt;
130}
131
134 llvm::SmallVector<std::optional<llvm::StringRef>> originalNames;
135 llvm::SmallVector<llvm::StringRef> existingNames;
136 llvm::SmallVector<llvm::SmallVector<std::string>> splitNameSuffixes;
137};
138
140template <typename GetNameAttrFn, typename GetSplitSuffixesFn>
142 mlir::ArrayRef<mlir::Type> origTypes, GetNameAttrFn &&getNameAttr,
143 GetSplitSuffixesFn &&getSplitSuffixes
144) {
146 info.originalNames.reserve(origTypes.size());
147 info.splitNameSuffixes.reserve(origTypes.size());
148 for (auto [i, type] : llvm::enumerate(origTypes)) {
149 if (std::optional<mlir::StringAttr> nameAttr = getNameAttr(i)) {
150 info.originalNames.push_back(nameAttr->getValue());
151 info.existingNames.push_back(nameAttr->getValue());
152 } else {
153 info.originalNames.push_back(std::nullopt);
154 }
155 info.splitNameSuffixes.push_back(getSplitSuffixes(type));
156 }
157 return info;
158}
159
163 mlir::ArrayAttr origAttrs, const llvm::SmallVector<size_t> &originalIdxToSize,
164 const llvm::SmallVector<mlir::Type> &newTypes, llvm::StringRef functionNameAttrName,
165 llvm::ArrayRef<std::optional<llvm::StringRef>> origNames = {},
166 llvm::ArrayRef<llvm::StringRef> existingNames = {},
167 llvm::ArrayRef<llvm::SmallVector<std::string>> splitNameSuffixes = {}
168) {
169 if (!origAttrs) {
170 return nullptr;
171 }
172 assert(originalIdxToSize.size() == origAttrs.size());
173 if (originalIdxToSize.size() == newTypes.size()) {
174 return nullptr;
175 }
176
177 llvm::SmallVector<mlir::Attribute> newAttrs;
178 llvm::StringSet<> usedNames;
179 if (!origNames.empty()) {
180 for (llvm::StringRef name : existingNames) {
181 usedNames.insert(name);
182 }
183 }
184
185 for (auto [i, s] : llvm::enumerate(originalIdxToSize)) {
186 mlir::Attribute attr = origAttrs[i];
187 if (!origNames.empty() && !splitNameSuffixes.empty() && s != 1 && origNames[i]) {
188 assert(i < splitNameSuffixes.size());
189 assert(splitNameSuffixes[i].size() == s);
190 auto dictAttr = llvm::cast<mlir::DictionaryAttr>(attr);
191 llvm::StringRef name = *origNames[i];
192 for (llvm::StringRef suffix : splitNameSuffixes[i]) {
193 std::string desiredName = (llvm::Twine(name) + suffix).str();
194 newAttrs.push_back(withFunctionNameAttr(
195 dictAttr, functionNameAttrName, reserveUniqueAttrName(usedNames, desiredName)
196 ));
197 }
198 continue;
199 }
200 newAttrs.append(s, attr);
201 }
202 return mlir::ArrayAttr::get(origAttrs.getContext(), newAttrs);
203}
204
212 mlir::Location loc, mlir::TypeRange newResultTypes, function::CallOp oldCall,
213 llvm::ArrayRef<mlir::ValueRange> mapOperands, mlir::ValueRange argOperands,
214 mlir::ConversionPatternRewriter &rewriter
215) {
216 llvm::SmallVector<mlir::Attribute> templateParams;
217 if (mlir::ArrayAttr templateParamsAttr = oldCall.getTemplateParamsAttr()) {
218 templateParams.append(templateParamsAttr.begin(), templateParamsAttr.end());
219 }
220
221 function::CallOp newCall;
222 if (oldCall.getMapOperands().empty()) {
223 newCall = function::CallOp::create(
224 rewriter, loc, newResultTypes, oldCall.getCalleeAttr(), argOperands, templateParams
225 );
226 } else {
227 newCall = function::CallOp::create(
228 rewriter, loc, newResultTypes, oldCall.getCalleeAttr(), mapOperands,
229 oldCall.getNumDimsPerMapAttr(), argOperands, templateParams
230 );
231 }
232 return preserveDiscardableAttrs(oldCall, newCall);
233}
234
236inline static mlir::Type replaceAffineMapArrayDimsWithWildcards(mlir::Type type) {
237 auto arrTy = llvm::dyn_cast<array::ArrayType>(type);
238 if (!arrTy) {
239 return type;
240 }
241
242 mlir::Builder builder(arrTy.getContext());
243 llvm::SmallVector<mlir::Attribute> dims;
244 dims.reserve(arrTy.getDimensionSizes().size());
245 for (mlir::Attribute dimSize : arrTy.getDimensionSizes()) {
246 if (llvm::isa<mlir::AffineMapAttr>(dimSize)) {
247 dims.push_back(builder.getIndexAttr(mlir::ShapedType::kDynamic));
248 } else {
249 dims.push_back(dimSize);
250 }
251 }
252
253 return arrTy.cloneWith(replaceAffineMapArrayDimsWithWildcards(arrTy.getElementType()), dims);
254}
255
259
260protected:
261 virtual llvm::SmallVector<mlir::Type> convertInputs(mlir::ArrayRef<mlir::Type> origTypes) = 0;
262 virtual llvm::SmallVector<mlir::Type> convertResults(mlir::ArrayRef<mlir::Type> origTypes) = 0;
263
264 virtual mlir::ArrayAttr
265 convertInputAttrs(mlir::ArrayAttr origAttrs, llvm::SmallVector<mlir::Type> newTypes) = 0;
266 virtual mlir::ArrayAttr
267 convertResultAttrs(mlir::ArrayAttr origAttrs, llvm::SmallVector<mlir::Type> newTypes) = 0;
268
269 virtual void processBlockArgs(mlir::Block &entryBlock, mlir::RewriterBase &rewriter) = 0;
270
271public:
272 virtual ~FunctionTypeConverter() = default;
273
274 void convert(function::FuncDefOp op, mlir::RewriterBase &rewriter) {
275 // Update in/out types of the function
276 mlir::FunctionType oldTy = op.getFunctionType();
277 llvm::SmallVector<mlir::Type> newInputs = convertInputs(oldTy.getInputs());
278 llvm::SmallVector<mlir::Type> newResults = convertResults(oldTy.getResults());
279 mlir::FunctionType newTy = mlir::FunctionType::get(
280 oldTy.getContext(), mlir::TypeRange(newInputs), mlir::TypeRange(newResults)
281 );
282 if (newTy == oldTy) {
283 return; // nothing to change
284 }
285
286 // Pre-condition: arg/result count equals corresponding attribute count
287 assert(!op.getResAttrsAttr() || op.getResAttrsAttr().size() == op.getNumResults());
288 assert(!op.getArgAttrsAttr() || op.getArgAttrsAttr().size() == op.getNumArguments());
289 rewriter.modifyOpInPlace(op, [&]() {
290 op.setFunctionType(newTy);
291
292 // If any input or result types were added, ensure the attributes are updated too.
293 if (mlir::ArrayAttr newArgAttrs = convertInputAttrs(op.getArgAttrsAttr(), newInputs)) {
294 op.setArgAttrsAttr(newArgAttrs);
295 }
296 if (mlir::ArrayAttr newResAttrs = convertResultAttrs(op.getResAttrsAttr(), newResults)) {
297 op.setResAttrsAttr(newResAttrs);
298 }
299 });
300 // Post-condition: arg/result count equals corresponding attribute count
301 assert(!op.getResAttrsAttr() || op.getResAttrsAttr().size() == op.getNumResults());
302 assert(!op.getArgAttrsAttr() || op.getArgAttrsAttr().size() == op.getNumArguments());
303
304 // If the function has a body, ensure the entry block arguments match the function inputs.
305 if (mlir::Region *body = op.getCallableRegion()) {
306 mlir::Block &entryBlock = body->front();
307 bool blockArgsNeedUpdate =
308 !std::cmp_equal(entryBlock.getNumArguments(), newInputs.size()) ||
309 llvm::any_of(llvm::zip_equal(entryBlock.getArgumentTypes(), newInputs), [](auto pair) {
310 return std::get<0>(pair) != std::get<1>(pair);
311 });
312 if (blockArgsNeedUpdate) {
313 processBlockArgs(entryBlock, rewriter);
314 // Post-condition: block args must match function inputs in both arity and type.
315 assert(std::cmp_equal(entryBlock.getNumArguments(), newInputs.size()));
316 for (unsigned i = 0, e = entryBlock.getNumArguments(); i < e; ++i) {
317 assert(entryBlock.getArgument(i).getType() == newInputs[i]);
318 }
319 }
320 }
321 }
322};
323
331template <
332 typename ImplClass, HasInterface<component::MemberRefOpInterface> MemberRefOpClass,
333 typename GenHeaderType, typename IdType>
334class SplitAggregateInMemberRefOp : public mlir::OpConversionPattern<MemberRefOpClass> {
335public:
337 using MemberInfo = std::pair<mlir::StringAttr, mlir::Type>;
339 using LocalMemberReplacementMap = llvm::DenseMap<IdType, MemberInfo>;
341 using MemberReplacementMap = llvm::DenseMap<
342 component::StructDefOp, llvm::DenseMap<mlir::StringAttr, LocalMemberReplacementMap>>;
343
344private:
345 mlir::SymbolTableCollection &tables;
346 const MemberReplacementMap &repMapRef;
347
348 // Static check to ensure the methods are implemented in all subclasses.
349 inline static void ensureImplementedAtCompile() {
350 static_assert(
351 sizeof(MemberRefOpClass) == 0,
352 "SplitAggregateInMemberRefOp not implemented for requested type."
353 );
354 }
355
356protected:
357 using OpAdaptor = typename MemberRefOpClass::Adaptor;
358
361 static GenHeaderType genHeader(MemberRefOpClass, mlir::ConversionPatternRewriter &) {
362 ensureImplementedAtCompile();
363 llvm_unreachable("must have concrete instantiation");
364 }
365
368 static void forId(
369 mlir::Location, GenHeaderType &, IdType, MemberInfo, OpAdaptor,
370 mlir::ConversionPatternRewriter &
371 ) {
372 ensureImplementedAtCompile();
373 llvm_unreachable("must have concrete instantiation");
374 }
375
376public:
377 // Suppress false positive from `clang-tidy`
378 // NOLINTNEXTLINE(bugprone-crtp-constructor-accessibility)
380 mlir::MLIRContext *ctx, mlir::SymbolTableCollection &symTables,
381 const MemberReplacementMap &memberRepMap
382 )
383 : mlir::OpConversionPattern<MemberRefOpClass>(ctx), tables(symTables),
384 repMapRef(memberRepMap) {}
385
386 static bool legal(MemberRefOpClass) {
387 ensureImplementedAtCompile();
388 llvm_unreachable("must have concrete instantiation");
389 return false;
390 }
391
392 mlir::LogicalResult matchAndRewrite(
393 MemberRefOpClass op, OpAdaptor adaptor, mlir::ConversionPatternRewriter &rewriter
394 ) const override {
395 if (ImplClass::legal(op)) {
396 return mlir::failure();
397 }
398 component::StructType tgtStructTy =
399 llvm::cast<component::MemberRefOpInterface>(op.getOperation()).getStructType();
400 assert(tgtStructTy);
401 auto tgtStructDef = tgtStructTy.getDefinition(tables, op);
402 assert(mlir::succeeded(tgtStructDef));
403
404 GenHeaderType prefixResult = ImplClass::genHeader(op, rewriter);
405
406 const LocalMemberReplacementMap &idToName =
407 repMapRef.at(tgtStructDef->get()).at(op.getMemberNameAttr().getAttr());
408 // Split the aggregate member into a series of scalar member ops.
409 for (const auto &[id, newMember] : idToName) {
410 ImplClass::forId(op.getLoc(), prefixResult, id, newMember, adaptor, rewriter);
411 }
412 if constexpr (requires { ImplClass::finalize(op, prefixResult, adaptor, rewriter); }) {
413 ImplClass::finalize(op, prefixResult, adaptor, rewriter);
414 }
415 if constexpr (requires { ImplClass::replacement(op, prefixResult, adaptor, rewriter); }) {
416 rewriter.replaceOp(op, ImplClass::replacement(op, prefixResult, adaptor, rewriter));
417 } else {
418 rewriter.eraseOp(op);
419 }
420 return mlir::success();
421 }
422};
423
424} // namespace llzk
General helper for converting a FuncDefOp by changing its input and/or result types and the associate...
virtual void processBlockArgs(mlir::Block &entryBlock, mlir::RewriterBase &rewriter)=0
virtual llvm::SmallVector< mlir::Type > convertResults(mlir::ArrayRef< mlir::Type > origTypes)=0
virtual mlir::ArrayAttr convertResultAttrs(mlir::ArrayAttr origAttrs, llvm::SmallVector< mlir::Type > newTypes)=0
void convert(function::FuncDefOp op, mlir::RewriterBase &rewriter)
virtual ~FunctionTypeConverter()=default
virtual mlir::ArrayAttr convertInputAttrs(mlir::ArrayAttr origAttrs, llvm::SmallVector< mlir::Type > newTypes)=0
virtual llvm::SmallVector< mlir::Type > convertInputs(mlir::ArrayRef< mlir::Type > origTypes)=0
llvm::DenseMap< component::StructDefOp, llvm::DenseMap< mlir::StringAttr, LocalMemberReplacementMap > > MemberReplacementMap
Maps struct -> original aggregate-type member name -> LocalMemberReplacementMap.
static bool legal(MemberRefOpClass)
std::pair< mlir::StringAttr, mlir::Type > MemberInfo
Scalar member name and type.
SplitAggregateInMemberRefOp(mlir::MLIRContext *ctx, mlir::SymbolTableCollection &symTables, const MemberReplacementMap &memberRepMap)
typename MemberRefOpClass::Adaptor OpAdaptor
mlir::LogicalResult matchAndRewrite(MemberRefOpClass op, OpAdaptor adaptor, mlir::ConversionPatternRewriter &rewriter) const override
static GenHeaderType genHeader(MemberRefOpClass, mlir::ConversionPatternRewriter &)
Executed at the start of rewrite() to (optionally) generate anything that should appear before the pe...
static void forId(mlir::Location, GenHeaderType &, IdType, MemberInfo, OpAdaptor, mlir::ConversionPatternRewriter &)
Executed for each scalar id in the aggregate type of the original member to generate the per-scalar o...
llvm::DenseMap< IdType, MemberInfo > LocalMemberReplacementMap
Maps a scalar element identifier within the aggregate to its new scalar member info.
::mlir::FailureOr< SymbolLookupResult< StructDefOp > > getDefinition(::mlir::SymbolTableCollection &symbolTable, ::mlir::Operation *op, bool reportMissing=true) const
Gets the struct op that defines this struct.
Definition Types.cpp:26
::mlir::SymbolRefAttr getCalleeAttr()
Definition Ops.h.inc:456
static CallOp create(::mlir::OpBuilder &builder, ::mlir::Location location, ::mlir::TypeRange resultTypes, ::mlir::SymbolRefAttr callee, ::mlir::ValueRange argOperands={}, ::llvm::ArrayRef<::mlir::Attribute > templateParams={})
Definition Ops.cpp.inc:604
::mlir::ArrayAttr getTemplateParamsAttr()
Definition Ops.h.inc:461
::mlir::OperandRangeRange getMapOperands()
Definition Ops.h.inc:434
::mlir::DenseI32ArrayAttr getNumDimsPerMapAttr()
Definition Ops.h.inc:466
::mlir::FunctionType getFunctionType()
Definition Ops.cpp.inc:1229
void setArgAttrsAttr(::mlir::ArrayAttr attr)
Definition Ops.h.inc:914
void setResAttrsAttr(::mlir::ArrayAttr attr)
Definition Ops.h.inc:918
::mlir::ArrayAttr getArgAttrsAttr()
Definition Ops.h.inc:894
void setFunctionType(::mlir::FunctionType attrValue)
Definition Ops.cpp.inc:1248
::mlir::Region * getCallableRegion()
Required by FunctionOpInterface.
Definition Ops.h.inc:1063
::mlir::ArrayAttr getResAttrsAttr()
Definition Ops.h.inc:899
Restricts a template parameter to Op classes that implement the given OpInterface.
Definition Concepts.h:20
constexpr char ARG_NAME_ATTR_NAME[]
Attribute name for source-level function argument names.
Definition Ops.h:36
constexpr char RES_NAME_ATTR_NAME[]
Attribute name for source-level function result names.
Definition Ops.h:39
mlir::DictionaryAttr withFunctionResNameAttr(mlir::DictionaryAttr attrs, llvm::StringRef name)
Return a copy of the given result attribute dictionary with function.res_name set to name.
OpClass replaceOpWithNewOp(Rewriter &rewriter, mlir::Operation *op, Args &&...args)
Wrapper for RewriterBase::replaceOpWithNewOp() that automatically copies discardable attributes (i....
mlir::DictionaryAttr withFunctionNameAttr(mlir::DictionaryAttr attrs, llvm::StringRef attrName, llvm::StringRef name)
Return a copy of the given function argument/result attribute dictionary with attrName set to name.
mlir::ArrayAttr replicateFunctionNameAttrsAsNeeded(mlir::ArrayAttr origAttrs, const llvm::SmallVector< size_t > &originalIdxToSize, const llvm::SmallVector< mlir::Type > &newTypes, llvm::StringRef functionNameAttrName, llvm::ArrayRef< std::optional< llvm::StringRef > > origNames={}, llvm::ArrayRef< llvm::StringRef > existingNames={}, llvm::ArrayRef< llvm::SmallVector< std::string > > splitNameSuffixes={})
Expand function arg/result attribute arrays to match a split signature, rewriting name attrs with the...
mlir::DictionaryAttr withFunctionArgNameAttr(mlir::DictionaryAttr attrs, llvm::StringRef name)
Return a copy of the given argument attribute dictionary with function.arg_name set to name.
OpTy preserveDiscardableAttrs(mlir::Operation *src, OpTy dst)
Copy discardable attributes from src to dst.
function::CallOp createCallPreservingInstantiationOperands(mlir::Location loc, mlir::TypeRange newResultTypes, function::CallOp oldCall, llvm::ArrayRef< mlir::ValueRange > mapOperands, mlir::ValueRange argOperands, mlir::ConversionPatternRewriter &rewriter)
Rebuild a function.call while preserving explicit instantiation state from oldCall.
SplitFunctionNameInfo collectSplitFunctionNameInfo(mlir::ArrayRef< mlir::Type > origTypes, GetNameAttrFn &&getNameAttr, GetSplitSuffixesFn &&getSplitSuffixes)
Collect function arg/result names and split suffixes from a list of original types.
std::optional< mlir::StringAttr > getAttrAtIndexWithName(mlir::ArrayAttr attrs, unsigned index, llvm::StringRef attrName)
Return the function arg/result attribute at index for the given name, if present.
OpTy preserveDiscardableAttrsExcept(mlir::Operation *src, OpTy dst, mlir::StringRef excludedAttr)
Copy discardable attributes from src to dst while omitting one internal attr.
std::string reserveUniqueAttrName(llvm::StringSet<> &usedNames, llvm::StringRef desiredName)
Reserve and return a unique function argument/result name based on desiredName.
Cached function arg/result names and split suffixes used while rewriting a function signature.
llvm::SmallVector< std::optional< llvm::StringRef > > originalNames
llvm::SmallVector< llvm::StringRef > existingNames
llvm::SmallVector< llvm::SmallVector< std::string > > splitNameSuffixes