LLZK 3.0.0
An open-source IR for Zero Knowledge (ZK) circuits
Loading...
Searching...
No Matches
ArrayToScalarPass.cpp
Go to the documentation of this file.
1//===-- ArrayToScalarPass.cpp -----------------------------------*- 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//===----------------------------------------------------------------------===//
20/// 2. Run a dialect conversion that does the following:
21///
22/// - Replace `MemberReadOp` and `MemberWriteOp` targeting the members that were split in step 1
23/// so they instead perform scalar reads and writes from the new members. The transformation is
24/// local to the current op. Therefore, when replacing the `MemberReadOp` a new array is
25/// created locally and all uses of the `MemberReadOp` are replaced with the new array Value,
26/// then each scalar member read is followed by scalar write into the new array. Similarly,
27/// when replacing a `MemberWriteOp`, each element in the array operand needs a scalar read
28/// from the array followed by a scalar write to the new member. Making only local changes
29/// keeps this step simple and later steps will optimize.
30///
31/// - Replace `ArrayLengthOp` with the constant size of the selected dimension.
32///
33/// - Remove element initialization from `CreateArrayOp` and instead insert a list of
34/// `WriteArrayOp` immediately following.
35///
36/// - Desugar `InsertArrayOp` and `ExtractArrayOp` into their element-wise scalar reads/writes.
37///
38/// - Split arrays to scalars in `FuncDefOp`, `CallOp`, and `ReturnOp` and insert the necessary
39/// create/read/write ops so the changes are as local as possible (just as described for
40/// `MemberReadOp` and `MemberWriteOp`)
41///
42/// 3. Replace branch-local reads (in `scf.if`) with the value written by a same-index write op that
43/// dominates the parent `scf.if` (because the passes below cannot handle that case).
44///
45/// 4. Fold read-only, constant-index accesses to constant global arrays without expanding them.
46///
47/// 5. Promote straight-line static arrays in one walk per block.
48///
49/// 6. Run restricted "sroa" pass to split each remaining array with linear size `N` into `N` size-1
50/// arrays (to prepare for "mem2reg" pass because its API cannot deal with splitting up memory).
51///
52/// 7. Run restricted "mem2reg" as a fallback for allocations with control flow or unsupported uses.
53///
54/// 8. Remove array allocations that become unread after memory promotion, then canonicalize local
55/// SSA values made dead by that cleanup.
56///
57/// Note: This transformation imposes a "last write wins" semantics on array elements. If
58/// different/configurable semantics are added in the future, some additional transformation would
59/// be necessary before/during this pass so that multiple writes to the same index can be handled
60/// properly while they still exist.
61///
62/// Note: This transformation will introduce a `nondet` op when there exists a read from an array
63/// index that was not earlier written to.
64///
65//===----------------------------------------------------------------------===//
66
95#include "llzk/Util/Walk.h"
96
97#include <mlir/Dialect/Arith/IR/Arith.h>
98#include <mlir/Dialect/SCF/IR/SCF.h>
99#include <mlir/Dialect/UB/IR/UBOps.h>
100#include <mlir/IR/BuiltinOps.h>
101#include <mlir/Pass/PassManager.h>
102#include <mlir/Transforms/DialectConversion.h>
103#include <mlir/Transforms/Passes.h>
104
105#include <llvm/Support/Debug.h>
106
107#include <optional>
108
109// Include the generated base pass class definitions.
110namespace llzk::array {
111#define GEN_PASS_DEF_ARRAYTOSCALARPASS
112#define GEN_PASS_DEF_STRAIGHTLINESTATICARRAYPROMOTIONPASS
114} // namespace llzk::array
116using namespace mlir;
117using namespace llzk;
118using namespace llzk::array;
119using namespace llzk::component;
120using namespace llzk::function;
121using namespace llzk::polymorphic;
123#define DEBUG_TYPE "llzk-array-to-scalar"
124
125namespace {
126
128inline ArrayType splittableArray(ArrayType at) {
129 return at.hasStaticShape() && !llvm::isa<NoneType>(at.getElementType()) ? at : nullptr;
131
133inline ArrayType splittableArray(Type t) {
134 if (ArrayType at = dyn_cast<ArrayType>(t)) {
135 return splittableArray(at);
136 } else {
137 return nullptr;
138 }
140
142
143inline bool containsSplittableArrayType(ArrayRef<Type> types) {
144 for (Type t : types) {
145 if (splittableArray(t)) {
146 return true;
147 }
148 }
149 return false;
150}
151
153template <typename T> bool containsSplittableArrayType(ValueTypeRange<T> types) {
154 for (Type t : types) {
155 if (splittableArray(t)) {
156 return true;
157 }
158 }
159 return false;
160}
161
164size_t splitArrayTypeTo(Type t, SmallVector<Type> &collect) {
165 if (ArrayType at = splittableArray(t)) {
166 size_t size = llzk::checkedCast<size_t>(at.getNumElements());
167 collect.append(size, at.getElementType());
168 return size;
169 } else {
170 collect.push_back(t);
171 return 1;
172 }
174
176template <typename TypeCollection>
177inline void splitArrayTypeTo(
178 TypeCollection types, SmallVector<Type> &collect, SmallVector<size_t> *originalIdxToSize
179) {
180 for (Type t : types) {
181 size_t count = splitArrayTypeTo(t, collect);
182 if (originalIdxToSize) {
183 originalIdxToSize->push_back(count);
184 }
185 }
186}
187
190template <typename TypeCollection>
191inline SmallVector<Type>
192splitArrayType(TypeCollection types, SmallVector<size_t> *originalIdxToSize = nullptr) {
193 SmallVector<Type> collect;
194 splitArrayTypeTo(types, collect, originalIdxToSize);
195 return collect;
196}
197
199static std::string formatSplitArrayIndexSuffix(ArrayAttr index) {
200 std::string suffix;
201 llvm::raw_string_ostream os(suffix);
202 for (Attribute attr : index) {
203 os << '[';
204 attr.print(os, true);
205 os << ']';
206 }
207 return suffix;
208}
209
211static SmallVector<std::string> getSplitArrayIndexSuffixes(Type type) {
212 SmallVector<std::string> suffixes;
213 if (ArrayType at = splittableArray(type)) {
214 std::optional<SmallVector<ArrayAttr>> indices = at.getSubelementIndices();
215 assert(indices.has_value() && "static-shape arrays must provide subelement indices");
216 suffixes.reserve(indices->size());
217 for (ArrayAttr index : *indices) {
218 suffixes.push_back(formatSplitArrayIndexSuffix(index));
219 }
220 }
221 return suffixes;
222}
223
225CallOp newCallOpWithSplitResults(
226 CallOp oldCall, CallOp::Adaptor adaptor, ConversionPatternRewriter &rewriter
227) {
228 OpBuilder::InsertionGuard guard(rewriter);
229 rewriter.setInsertionPointAfter(oldCall);
230
231 Operation::result_range oldResults = oldCall.getResults();
233 oldCall.getLoc(), splitArrayType(oldResults.getTypes()), oldCall, adaptor.getMapOperands(),
234 adaptor.getArgOperands(), rewriter
235 );
236
237 auto newResults = newCall.getResults().begin();
238 SmallVector<Value> replacements;
239 replacements.reserve(oldResults.size());
240 for (Value oldVal : oldResults) {
241 if (ArrayType at = splittableArray(oldVal.getType())) {
242 Location loc = oldVal.getLoc();
243 // Generate a `CreateArrayOp` to replace the original array result.
244 auto newArray = CreateArrayOp::create(rewriter, loc, at);
245 replacements.push_back(newArray);
246
247 // For all indices in the ArrayType (i.e., the element count), write the next
248 // result from the new CallOp to the new array.
249 std::optional<SmallVector<ArrayAttr>> allIndices = at.getSubelementIndices();
250 assert(allIndices); // follows from legal() check
251 assert(std::cmp_equal(allIndices->size(), at.getNumElements()));
252 for (ArrayAttr subIdx : allIndices.value()) {
253 ArrayAccessOpInterface::genWrite(rewriter, loc, newArray, subIdx, *newResults);
254 newResults++;
255 }
256 } else {
257 replacements.push_back(*newResults);
258 newResults++;
259 }
260 }
261 // Use the conversion rewriter's operation-level replacement API. Replacing
262 // individual results before erasing the call causes it to register a second
263 // replacement for those values.
264 rewriter.replaceOp(oldCall, replacements);
265
266 return newCall;
267}
268
271void processInputOperand(
272 Location loc, Value operand, SmallVector<Value> &newOperands,
273 ConversionPatternRewriter &rewriter
274) {
275 if (ArrayType at = splittableArray(operand.getType())) {
276 std::optional<SmallVector<ArrayAttr>> indices = at.getSubelementIndices();
277 assert(indices.has_value() && "passed earlier hasStaticShape() check");
278 for (ArrayAttr index : indices.value()) {
279 newOperands.push_back(ArrayAccessOpInterface::genRead(rewriter, loc, operand, index));
280 }
281 } else {
282 newOperands.push_back(operand);
283 }
284}
285
287void processInputOperands(
288 ValueRange operands, MutableOperandRange outputOpRef, Operation *op,
289 ConversionPatternRewriter &rewriter
290) {
291 SmallVector<Value> newOperands;
292 for (Value v : operands) {
293 processInputOperand(op->getLoc(), v, newOperands, rewriter);
294 }
295 rewriter.modifyOpInPlace(op, [&outputOpRef, &newOperands]() {
296 outputOpRef.assign(ValueRange(newOperands));
297 });
298}
299
300namespace {
301
302enum Direction : std::uint8_t {
304 SMALL_TO_LARGE,
306 LARGE_TO_SMALL,
307};
308
311template <Direction dir>
312inline void rewriteImpl(
313 ArrayAccessOpInterface op, ArrayType smallType, Value smallArr, Value largeArr,
314 ConversionPatternRewriter &rewriter
315) {
316 assert(smallType); // follows from legal() check
317 Location loc = op.getLoc();
318 MLIRContext *ctx = op.getContext();
319
320 ArrayAttr indexAsAttr = op.indexOperandsToAttributeArray();
321 assert(indexAsAttr); // follows from legal() check
322
323 // For all indices in the ArrayType (i.e., the element count), read from one array into the other
324 // (depending on direction flag).
325 std::optional<SmallVector<ArrayAttr>> subIndices = smallType.getSubelementIndices();
326 assert(subIndices); // follows from legal() check
327 assert(std::cmp_equal(subIndices->size(), smallType.getNumElements()));
328 for (ArrayAttr indexingTail : subIndices.value()) {
329 SmallVector<Attribute> joined;
330 joined.append(indexAsAttr.begin(), indexAsAttr.end());
331 joined.append(indexingTail.begin(), indexingTail.end());
332 ArrayAttr fullIndex = ArrayAttr::get(ctx, joined);
333
334 if constexpr (dir == Direction::SMALL_TO_LARGE) {
335 auto init = ArrayAccessOpInterface::genRead(rewriter, loc, smallArr, indexingTail);
336 ArrayAccessOpInterface::genWrite(rewriter, loc, largeArr, fullIndex, init);
337 } else if constexpr (dir == Direction::LARGE_TO_SMALL) {
338 auto init = ArrayAccessOpInterface::genRead(rewriter, loc, largeArr, fullIndex);
339 ArrayAccessOpInterface::genWrite(rewriter, loc, smallArr, indexingTail, init);
340 }
341 }
342}
343
344} // namespace
345
347class SplitInsertArrayOp : public OpConversionPattern<InsertArrayOp> {
348public:
349 using OpConversionPattern<InsertArrayOp>::OpConversionPattern;
350
351 static bool legal(InsertArrayOp op) {
352 return !containsSplittableArrayType(op.getRvalue().getType());
353 }
354
355 LogicalResult matchAndRewrite(
356 InsertArrayOp op, OpAdaptor adaptor, ConversionPatternRewriter &rewriter
357 ) const override {
358 if (legal(op)) {
359 return failure();
360 }
361 ArrayType at = splittableArray(op.getRvalue().getType());
362 rewriteImpl<SMALL_TO_LARGE>(
363 llvm::cast<ArrayAccessOpInterface>(op.getOperation()), at, adaptor.getRvalue(),
364 adaptor.getArrRef(), rewriter
365 );
366 rewriter.eraseOp(op);
367 return success();
368 }
369};
370
372class SplitExtractArrayOp : public OpConversionPattern<ExtractArrayOp> {
373public:
374 using OpConversionPattern<ExtractArrayOp>::OpConversionPattern;
375
376 static bool legal(ExtractArrayOp op) {
377 return !containsSplittableArrayType(op.getResult().getType());
378 }
379
380 LogicalResult matchAndRewrite(
381 ExtractArrayOp op, OpAdaptor adaptor, ConversionPatternRewriter &rewriter
382 ) const override {
383 if (legal(op)) {
384 return failure();
385 }
386 ArrayType at = splittableArray(op.getResult().getType());
387 // Generate `CreateArrayOp` in place of the current op.
388 auto newArray = replaceOpWithNewOp<CreateArrayOp>(rewriter, op, at);
389 rewriteImpl<LARGE_TO_SMALL>(
390 llvm::cast<ArrayAccessOpInterface>(op.getOperation()), at, newArray, adaptor.getArrRef(),
391 rewriter
392 );
393 return success();
394 }
395};
396
398class SplitInitFromCreateArrayOp : public OpConversionPattern<CreateArrayOp> {
399public:
400 using OpConversionPattern<CreateArrayOp>::OpConversionPattern;
401
402 static bool legal(CreateArrayOp op) { return op.getElements().empty(); }
403
404 LogicalResult matchAndRewrite(
405 CreateArrayOp op, OpAdaptor adaptor, ConversionPatternRewriter &rewriter
406 ) const override {
407 if (legal(op)) {
408 return failure();
409 }
410 // Remove elements from `op`
411 rewriter.modifyOpInPlace(op, [&op]() { op.getElementsMutable().clear(); });
412 // Generate an individual write for each initialization element
413 rewriter.setInsertionPointAfter(op);
414 Location loc = op.getLoc();
415 ArrayIndexGen idxGen = ArrayIndexGen::from(op.getType());
416 for (auto [i, init] : llvm::enumerate(adaptor.getElements())) {
417 // Convert the linear index 'i' into a multi-dim index
418 std::optional<SmallVector<Value>> multiDimIdxVals =
419 idxGen.delinearize(llzk::checkedCast<int64_t>(i), loc, rewriter);
420 // ASSERT: CreateArrayOp verifier ensures the number of elements provided matches the full
421 // linear array size so delinearization of `i` will not fail.
422 assert(multiDimIdxVals.has_value());
423 // Create the write
424 WriteArrayOp::create(rewriter, loc, op.getResult(), ValueRange(*multiDimIdxVals), init);
425 }
426 return success();
427 }
428};
429
431class SplitArrayInFuncDefOp : public OpConversionPattern<FuncDefOp> {
432public:
433 using OpConversionPattern<FuncDefOp>::OpConversionPattern;
434
435 inline static bool legal(FuncDefOp op) {
436 return !containsSplittableArrayType(op.getArgumentTypes()) &&
437 !containsSplittableArrayType(op.getResultTypes());
438 }
439
440 LogicalResult
441 matchAndRewrite(FuncDefOp op, OpAdaptor, ConversionPatternRewriter &rewriter) const override {
442 if (legal(op)) {
443 return failure();
444 }
445 // Update in/out types of the function to replace arrays with scalars
446 class Impl : public FunctionTypeConverter {
447 SmallVector<size_t> originalInputIdxToSize, originalResultIdxToSize;
448 SplitFunctionNameInfo inputNameInfo;
449 SplitFunctionNameInfo resultNameInfo;
450
451 protected:
452 SmallVector<Type> convertInputs(ArrayRef<Type> origTypes) override {
453 return splitArrayType(origTypes, &originalInputIdxToSize);
454 }
455 SmallVector<Type> convertResults(ArrayRef<Type> origTypes) override {
456 return splitArrayType(origTypes, &originalResultIdxToSize);
457 }
458 ArrayAttr convertInputAttrs(ArrayAttr origAttrs, SmallVector<Type> newTypes) override {
460 origAttrs, originalInputIdxToSize, newTypes, ARG_NAME_ATTR_NAME,
461 inputNameInfo.originalNames, inputNameInfo.existingNames,
462 inputNameInfo.splitNameSuffixes
463 );
464 }
465 ArrayAttr convertResultAttrs(ArrayAttr origAttrs, SmallVector<Type> newTypes) override {
467 origAttrs, originalResultIdxToSize, newTypes, RES_NAME_ATTR_NAME,
468 resultNameInfo.originalNames, resultNameInfo.existingNames,
469 resultNameInfo.splitNameSuffixes
470 );
471 }
472
477 void processBlockArgs(Block &entryBlock, RewriterBase &rewriter) override {
478 OpBuilder::InsertionGuard guard(rewriter);
479 rewriter.setInsertionPointToStart(&entryBlock);
480
481 for (unsigned i = 0; i < entryBlock.getNumArguments();) {
482 Value oldV = entryBlock.getArgument(i);
483 if (ArrayType at = splittableArray(oldV.getType())) {
484 Location loc = oldV.getLoc();
485 // Generate `CreateArrayOp` and replace uses of the argument with it.
486 auto newArray = CreateArrayOp::create(rewriter, loc, at);
487 // ConversionPatternRewriter defers replacements while rollback is enabled, but
488 // eraseArgument destroys the old argument immediately. Update the use-list before
489 // erasing that argument.
490 oldV.replaceAllUsesWith(newArray);
491 // Remove the argument from the block
492 entryBlock.eraseArgument(i);
493 // For all indices in the ArrayType (i.e., the element count), generate a new block
494 // argument and a write of that argument to the new array.
495 std::optional<SmallVector<ArrayAttr>> allIndices = at.getSubelementIndices();
496 assert(allIndices); // follows from legal() check
497 assert(std::cmp_equal(allIndices->size(), at.getNumElements()));
498 for (ArrayAttr subIdx : allIndices.value()) {
499 BlockArgument newArg = entryBlock.insertArgument(i, at.getElementType(), loc);
500 ArrayAccessOpInterface::genWrite(rewriter, loc, newArray, subIdx, newArg);
501 ++i;
502 }
503 } else {
504 ++i;
505 }
506 }
507 }
508
509 public:
510 Impl(FuncDefOp op) {
511 ArrayAttr resultAttrs = op.getAllResultAttrs();
512 inputNameInfo = collectSplitFunctionNameInfo(op.getArgumentTypes(), [&](unsigned i) {
513 return op.getArgNameAttr(i);
514 }, getSplitArrayIndexSuffixes);
515 resultNameInfo = collectSplitFunctionNameInfo(op.getResultTypes(), [&](unsigned i) {
516 return getAttrAtIndexWithName(resultAttrs, i, RES_NAME_ATTR_NAME);
517 }, getSplitArrayIndexSuffixes);
518 }
519 };
520 Impl(op).convert(op, rewriter);
521 return success();
522 }
523};
524
526class SplitArrayInReturnOp : public OpConversionPattern<ReturnOp> {
527public:
528 using OpConversionPattern<ReturnOp>::OpConversionPattern;
529
530 inline static bool legal(ReturnOp op) {
531 return !containsSplittableArrayType(op.getOperands().getTypes());
532 }
533
534 LogicalResult matchAndRewrite(
535 ReturnOp op, OpAdaptor adaptor, ConversionPatternRewriter &rewriter
536 ) const override {
537 if (legal(op)) {
538 return failure();
539 }
540 processInputOperands(adaptor.getOperands(), op.getOperandsMutable(), op, rewriter);
541 return success();
542 }
543};
544
546class SplitArrayInCallOp : public OpConversionPattern<CallOp> {
547public:
548 using OpConversionPattern<CallOp>::OpConversionPattern;
549
550 inline static bool legal(CallOp op) {
551 return !containsSplittableArrayType(op.getArgOperands().getTypes()) &&
552 !containsSplittableArrayType(op.getResultTypes());
553 }
554
555 LogicalResult matchAndRewrite(
556 CallOp op, OpAdaptor adaptor, ConversionPatternRewriter &rewriter
557 ) const override {
558 if (legal(op)) {
559 return failure();
560 }
561 // Create new CallOp with split results first so, then process its inputs to split types
562 CallOp newCall = newCallOpWithSplitResults(op, adaptor, rewriter);
563 processInputOperands(
564 newCall.getArgOperands(), newCall.getArgOperandsMutable(), newCall, rewriter
565 );
566 return success();
567 }
568};
569
571class ReplaceKnownArrayLengthOp : public OpConversionPattern<ArrayLengthOp> {
572public:
573 using OpConversionPattern<ArrayLengthOp>::OpConversionPattern;
574
576 static std::optional<llvm::APInt> getDimSizeIfKnown(Value dimIdx, ArrayType baseArrType) {
577 if (baseArrType.hasStaticShape()) {
578 llvm::APInt idxAP;
579 if (mlir::matchPattern(dimIdx, mlir::m_ConstantInt(&idxAP))) {
580 std::optional<int64_t> signedIdx = idxAP.trySExtValue();
581 if (!signedIdx || *signedIdx < 0) {
582 return std::nullopt;
583 }
584 size_t idx = llzk::checkedCast<size_t>(*signedIdx);
585 ArrayRef<Attribute> dimSizes = baseArrType.getDimensionSizes();
586 if (idx >= dimSizes.size()) {
587 return std::nullopt;
588 }
589 Attribute dimSizeAttr = dimSizes[idx];
590 if (mlir::matchPattern(dimSizeAttr, mlir::m_ConstantInt(&idxAP))) {
591 return idxAP;
592 }
593 }
594 }
595 return std::nullopt;
596 }
597
598 inline static bool legal(ArrayLengthOp op) {
599 // rewrite() can only work with constant dim size, i.e., must consider it legal otherwise
600 return !getDimSizeIfKnown(op.getDim(), op.getArrRefType()).has_value();
601 }
602
603 LogicalResult matchAndRewrite(
604 ArrayLengthOp op, OpAdaptor adaptor, ConversionPatternRewriter &rewriter
605 ) const override {
606 if (legal(op)) {
607 return failure();
608 }
609 ArrayType arrTy = dyn_cast<ArrayType>(adaptor.getArrRef().getType());
610 assert(arrTy); // must have array type per ODS spec of ArrayLengthOp
611 std::optional<llvm::APInt> len = getDimSizeIfKnown(adaptor.getDim(), arrTy);
612 assert(len.has_value()); // follows from legal() check
614 return success();
615 }
616};
617
619using MemberInfo = std::pair<StringAttr, Type>;
621using LocalMemberReplacementMap = DenseMap<ArrayAttr, MemberInfo>;
623using MemberReplacementMap = DenseMap<StructDefOp, DenseMap<StringAttr, LocalMemberReplacementMap>>;
624
626class SplitArrayInMemberDefOp : public OpConversionPattern<MemberDefOp> {
627 SymbolTableCollection &tables;
628 MemberReplacementMap &repMapRef;
629
630public:
631 SplitArrayInMemberDefOp(
632 MLIRContext *ctx, SymbolTableCollection &symTables, MemberReplacementMap &memberRepMap
633 )
634 : OpConversionPattern<MemberDefOp>(ctx), tables(symTables), repMapRef(memberRepMap) {}
635
636 inline static bool legal(MemberDefOp op) { return !containsSplittableArrayType(op.getType()); }
637
638 LogicalResult
639 matchAndRewrite(MemberDefOp op, OpAdaptor, ConversionPatternRewriter &rewriter) const override {
640 if (legal(op)) {
641 return failure();
642 }
643 StructDefOp inStruct = op->getParentOfType<StructDefOp>();
644 assert(inStruct);
645 LocalMemberReplacementMap &localRepMapRef = repMapRef[inStruct][op.getSymNameAttr()];
646
647 ArrayType arrTy = dyn_cast<ArrayType>(op.getType());
648 assert(arrTy); // follows from legal() check
649 auto subIdxs = arrTy.getSubelementIndices();
650 assert(subIdxs.has_value());
651 Type elemTy = arrTy.getElementType();
652
653 SymbolTable &structSymbolTable = tables.getSymbolTable(inStruct);
654 for (ArrayAttr idx : subIdxs.value()) {
655 // Create scalar version of the member
656 MemberDefOp newMember = MemberDefOp::create(
657 rewriter, op.getLoc(), op.getSymNameAttr(), elemTy, op.getSignal(), op.getColumn()
658 );
659 newMember.setPublicAttr(op.hasPublicAttr());
660 // Use SymbolTable to give it a unique name and store to the replacement map
661 localRepMapRef[idx] = std::make_pair(structSymbolTable.insert(newMember), elemTy);
662 }
663 rewriter.eraseOp(op);
664 return success();
665 }
666};
667
669class SplitArrayInMemberWriteOp : public SplitAggregateInMemberRefOp<
670 SplitArrayInMemberWriteOp, MemberWriteOp, void *, ArrayAttr> {
671public:
672 using SplitAggregateInMemberRefOp<
673 SplitArrayInMemberWriteOp, MemberWriteOp, void *, ArrayAttr>::SplitAggregateInMemberRefOp;
674
675 static bool legal(MemberWriteOp op) {
676 return !containsSplittableArrayType(op.getVal().getType());
677 }
678
679 static void *genHeader(MemberWriteOp, ConversionPatternRewriter &) { return nullptr; }
680
681 static void forId(
682 Location loc, void *, ArrayAttr idx, MemberInfo newMember, OpAdaptor adaptor,
683 ConversionPatternRewriter &rewriter
684 ) {
685 Value scalarRead = ArrayAccessOpInterface::genRead(rewriter, loc, adaptor.getVal(), idx);
687 rewriter, loc, adaptor.getComponent(), FlatSymbolRefAttr::get(newMember.first), scalarRead
688 );
689 }
690};
691
694class SplitArrayInMemberReadOp
696 SplitArrayInMemberReadOp, MemberReadOp, CreateArrayOp, ArrayAttr> {
697public:
698 using SplitAggregateInMemberRefOp<
699 SplitArrayInMemberReadOp, MemberReadOp, CreateArrayOp,
700 ArrayAttr>::SplitAggregateInMemberRefOp;
701
702 static bool legal(MemberReadOp op) {
703 return !containsSplittableArrayType(op.getResult().getType());
704 }
705
706 static CreateArrayOp genHeader(MemberReadOp op, ConversionPatternRewriter &rewriter) {
707 return CreateArrayOp::create(rewriter, op.getLoc(), llvm::cast<ArrayType>(op.getType()));
708 }
709
711 static CreateArrayOp
712 replacement(MemberReadOp, CreateArrayOp newArray, OpAdaptor, ConversionPatternRewriter &) {
713 return newArray;
714 }
715
716 static void forId(
717 Location loc, CreateArrayOp newArray, ArrayAttr idx, MemberInfo newMember, OpAdaptor adaptor,
718 ConversionPatternRewriter &rewriter
719 ) {
720 MemberReadOp scalarRead = MemberReadOp::create(
721 rewriter, loc, newMember.second, adaptor.getComponent(), newMember.first
722 );
723 ArrayAccessOpInterface::genWrite(rewriter, loc, newArray, idx, scalarRead);
724 }
725};
726
728static void baseTargetSetup(ConversionTarget &target) {
729 target.addLegalDialect<
734 scf::SCFDialect>();
735 target.addLegalOp<ModuleOp>();
736}
737
739class NondetToNewArray : public OpConversionPattern<NonDetOp> {
740 using OpConversionPattern<NonDetOp>::OpConversionPattern;
741 LogicalResult matchAndRewrite(
742 NonDetOp nondetOp, OpAdaptor, ConversionPatternRewriter &rewriter
743 ) const override {
744 if (auto at = dyn_cast<ArrayType>(nondetOp.getType())) {
745 auto wildcardTy = llvm::cast<ArrayType>(replaceAffineMapArrayDimsWithWildcards(at));
746 auto newArray = preserveDiscardableAttrs(
747 nondetOp, CreateArrayOp::create(rewriter, nondetOp.getLoc(), wildcardTy)
748 );
749 if (wildcardTy == at) {
750 rewriter.replaceOp(nondetOp, newArray);
751 } else {
752 auto cast = UnifiableCastOp::create(rewriter, nondetOp.getLoc(), at, newArray);
753 rewriter.replaceOp(nondetOp, cast.getResult());
754 }
755 return success();
756 }
757 return failure();
758 }
759};
760
762static LogicalResult step0(ModuleOp modOp) {
763 MLIRContext *ctx = modOp.getContext();
764 RewritePatternSet patterns {ctx};
765 patterns.add<NondetToNewArray>(ctx);
766 ConversionTarget target {*ctx};
767
768 baseTargetSetup(target);
769 target.addDynamicallyLegalOp<NonDetOp>([](NonDetOp op) { return !isa<ArrayType>(op.getType()); });
770
771 return applyFullConversion(modOp, target, std::move(patterns));
772}
773
775static LogicalResult
776step1(ModuleOp modOp, SymbolTableCollection &symTables, MemberReplacementMap &memberRepMap) {
777 MLIRContext *ctx = modOp.getContext();
778
779 RewritePatternSet patterns(ctx);
780
781 patterns.add<SplitArrayInMemberDefOp>(ctx, symTables, memberRepMap);
782
783 ConversionTarget target(*ctx);
784 baseTargetSetup(target);
785 target.addDynamicallyLegalOp<MemberDefOp>(SplitArrayInMemberDefOp::legal);
786
787 LLVM_DEBUG(llvm::dbgs() << "Begin step 1: split array-type members\n";);
788 return applyFullConversion(modOp, target, std::move(patterns));
789}
790
793static LogicalResult
794step2(ModuleOp modOp, SymbolTableCollection &symTables, const MemberReplacementMap &memberRepMap) {
795 MLIRContext *ctx = modOp.getContext();
796
797 RewritePatternSet patterns(ctx);
798 patterns.add<
799 // clang-format off
800 SplitInitFromCreateArrayOp,
801 SplitInsertArrayOp,
802 SplitExtractArrayOp,
803 SplitArrayInFuncDefOp,
804 SplitArrayInReturnOp,
805 SplitArrayInCallOp,
806 ReplaceKnownArrayLengthOp
807 // clang-format on
808 >(ctx);
809
810 patterns.add<
811 // clang-format off
812 SplitArrayInMemberWriteOp,
813 SplitArrayInMemberReadOp
814 // clang-format on
815 >(ctx, symTables, memberRepMap);
816
817 ConversionTarget target(*ctx);
818 baseTargetSetup(target);
819 target.addDynamicallyLegalOp<CreateArrayOp>(SplitInitFromCreateArrayOp::legal);
820 target.addDynamicallyLegalOp<InsertArrayOp>(SplitInsertArrayOp::legal);
821 target.addDynamicallyLegalOp<ExtractArrayOp>(SplitExtractArrayOp::legal);
822 target.addDynamicallyLegalOp<FuncDefOp>(SplitArrayInFuncDefOp::legal);
823 target.addDynamicallyLegalOp<ReturnOp>(SplitArrayInReturnOp::legal);
824 target.addDynamicallyLegalOp<CallOp>(SplitArrayInCallOp::legal);
825 target.addDynamicallyLegalOp<ArrayLengthOp>(ReplaceKnownArrayLengthOp::legal);
826 target.addDynamicallyLegalOp<MemberWriteOp>(SplitArrayInMemberWriteOp::legal);
827 target.addDynamicallyLegalOp<MemberReadOp>(SplitArrayInMemberReadOp::legal);
828
829 LLVM_DEBUG(llvm::dbgs() << "Begin step 2: update/split other array ops\n";);
830 return applyFullConversion(modOp, target, std::move(patterns));
831}
832
834inline static ArrayAttr getIndexAsAttr(ArrayAccessOpInterface op) {
836}
837
839static bool mayWriteToIndex(WriteArrayOp writeOp, ArrayAttr index) {
840 ArrayAttr writeIndex = getIndexAsAttr(writeOp);
841 return !writeIndex || writeIndex == index;
842}
843
845static bool hasEarlierWriteInBlock(ReadArrayOp readOp, ArrayAttr readIndex) {
846 Value arrRef = readOp.getArrRef();
847 for (Operation &op : *readOp->getBlock()) {
848 if (&op == readOp.getOperation()) {
849 return false;
850 }
851
852 if (auto writeOp = dyn_cast<WriteArrayOp>(&op)) {
853 if (writeOp.getArrRef() == arrRef && mayWriteToIndex(writeOp, readIndex)) {
854 return true;
855 }
856 continue;
857 }
858
859 // Writes nested inside earlier operations may conditionally clobber the read's value.
860 if (op.walk([arrRef, readIndex](WriteArrayOp writeOp) {
861 if (writeOp.getArrRef() == arrRef && mayWriteToIndex(writeOp, readIndex)) {
862 return WalkResult::interrupt();
863 }
864 return WalkResult::advance();
865 }).wasInterrupted()) {
866 return true;
867 }
868 }
869 return false;
870}
871
873static std::optional<WriteArrayOp> findPrecedingWriteForIfRead(ReadArrayOp readOp) {
874 ArrayAttr readIndex = getIndexAsAttr(readOp);
875 if (!readIndex) {
876 return std::nullopt;
877 }
878
879 // Only handle reads that are direct children of an `scf.if` branch.
880 auto ifOp = readOp->getParentOfType<scf::IfOp>();
881 if (!ifOp || readOp->getBlock()->getParentOp() != ifOp.getOperation()) {
882 return std::nullopt;
883 }
884 if (hasEarlierWriteInBlock(readOp, readIndex)) {
885 return std::nullopt;
886 }
887
888 Block *ifBlock = ifOp->getBlock();
889 if (!ifBlock) {
890 return std::nullopt;
891 }
892
893 Value arrRef = readOp.getArrRef();
894 WriteArrayOp replacement;
895 for (Operation &op : *ifBlock) {
896 if (&op == ifOp.getOperation()) {
897 break;
898 }
899
900 if (auto writeOp = dyn_cast<WriteArrayOp>(&op)) {
901 if (writeOp.getArrRef() != arrRef) {
902 continue;
903 }
904
905 if (mayWriteToIndex(writeOp, readIndex)) {
906 ArrayAttr writeIndex = getIndexAsAttr(writeOp);
907 replacement = writeIndex == readIndex ? writeOp : WriteArrayOp();
908 }
909 continue;
910 }
911
912 // A nested write before the `scf.if` may overwrite the current candidate.
913 if (op.walk([arrRef, readIndex, &replacement](WriteArrayOp writeOp) {
914 if (writeOp.getArrRef() == arrRef && mayWriteToIndex(writeOp, readIndex)) {
915 replacement = WriteArrayOp();
916 return WalkResult::interrupt();
917 }
918 return WalkResult::advance();
919 }).wasInterrupted()) {
920 continue;
921 }
922 }
923
924 return replacement ? std::make_optional(replacement) : std::nullopt;
925}
926
929static void step3(ModuleOp modOp) {
930 SmallVector<std::pair<ReadArrayOp, Value>> replacements;
931 modOp.walk([&replacements](ReadArrayOp readOp) {
932 if (std::optional<WriteArrayOp> writeOp = findPrecedingWriteForIfRead(readOp)) {
933 replacements.emplace_back(readOp, writeOp->getRvalue());
934 }
935 });
936
937 for (auto [readOp, value] : replacements) {
938 readOp.getResult().replaceAllUsesWith(value);
939 readOp.erase();
940 }
941}
942
946static void foldConstantGlobalArrayReads(ModuleOp module) {
947 SymbolTableCollection tables;
948 // Rewriting erases later array-read operations, so finish walking before mutating the IR.
949 auto foldRead = [&tables](global::GlobalReadOp globalRead) {
950 auto arrayType = llvm::dyn_cast<ArrayType>(globalRead.getType());
951 if (!arrayType || !arrayType.hasStaticShape()) {
952 return;
953 }
954 auto definition =
955 llvm::cast<global::GlobalRefOpInterface>(globalRead.getOperation()).getGlobalDefOp(tables);
956 if (failed(definition) || !definition->get().isConstant()) {
957 return;
958 }
959 auto initializer =
960 llvm::dyn_cast_if_present<ArrayAttr>(definition->get().getInitialValueAttr());
961 if (!initializer) {
962 return;
963 }
964
965 SmallVector<std::pair<ReadArrayOp, Attribute>> reads;
966 auto indexGen = ArrayIndexGen::from(arrayType);
967 for (Operation *user : globalRead.getResult().getUsers()) {
968 auto read = llvm::dyn_cast<ReadArrayOp>(user);
969 if (!read || read.getArrRef() != globalRead.getResult()) {
970 return;
971 }
972 ArrayAttr indices = getIndexAsAttr(read);
973 if (!indices) {
974 return;
975 }
976 std::optional<int64_t> index = indexGen.linearize(indices.getValue());
977 if (!index || *index < 0 || checkedCast<size_t>(*index) >= initializer.size()) {
978 return;
979 }
980 Attribute element = initializer[*index];
981 auto typed = llvm::dyn_cast<TypedAttr>(element);
982 if (!typed || typed.getType() != read.getType() ||
983 !llvm::isa<IntegerAttr, felt::FeltConstAttr, StringAttr>(element)) {
984 return;
985 }
986 reads.emplace_back(read, element);
987 }
988
989 for (auto [read, element] : reads) {
990 OpBuilder builder(read);
991 Value constant;
992 if (auto integer = llvm::dyn_cast<IntegerAttr>(element)) {
993 constant = arith::ConstantOp::create(builder, read.getLoc(), integer);
994 } else if (auto felt = llvm::dyn_cast<felt::FeltConstAttr>(element)) {
995 constant = felt::FeltConstantOp::create(builder, read.getLoc(), felt);
996 } else {
998 builder, read.getLoc(), read.getType(), llvm::cast<StringAttr>(element)
999 );
1000 }
1001 read.getResult().replaceAllUsesWith(constant);
1002 read.erase();
1003 }
1004 // The declaration proves immutability even if this read omits its optional `const` marker.
1005 globalRead.erase();
1006 };
1007 for (global::GlobalReadOp read : walkCollect<global::GlobalReadOp>(module)) {
1008 foldRead(read);
1009 }
1010}
1011
1014static bool hasStraightLineRegionPath(Block *allocationBlock, Block *accessBlock) {
1015 for (Block *block = accessBlock; block != allocationBlock;) {
1016 Operation *parent = block->getParentOp();
1017 if (!parent || !isa<scf::ExecuteRegionOp>(parent) ||
1018 !llvm::hasSingleElement(*block->getParent())) {
1019 return false;
1020 }
1021 block = parent->getBlock();
1022 }
1023 return true;
1024}
1025
1028struct StraightLineStaticAccess {
1030 Operation *op;
1032 Attribute index;
1034 Block *block;
1035};
1036
1041static bool collectStraightLineStaticElements(
1042 CreateArrayOp alloc, DenseMap<Attribute, Block *> &elementBlocks,
1043 SmallVectorImpl<StraightLineStaticAccess> &accesses
1044) {
1045 ArrayType type = alloc.getType();
1046 if (!type.hasStaticShape() || !alloc.getElements().empty()) {
1047 return false;
1048 }
1049
1050 DenseSet<Attribute> ineligibleElements;
1051 for (OpOperand &use : alloc.getResult().getUses()) {
1052 Operation *owner = use.getOwner();
1053 if (use.getOperandNumber() != 0) {
1054 return false;
1055 }
1056
1057 ArrayAttr index;
1058 if (auto read = dyn_cast<ReadArrayOp>(owner)) {
1059 index = mlir::cast<ArrayAccessOpInterface>(owner).indexOperandsToAttributeArray();
1060 if (!index || read.getResult().getType() != type.getElementType()) {
1061 return false;
1062 }
1063 } else if (auto write = dyn_cast<WriteArrayOp>(owner)) {
1064 index = mlir::cast<ArrayAccessOpInterface>(owner).indexOperandsToAttributeArray();
1065 if (!index || write.getRvalue().getType() != type.getElementType()) {
1066 return false;
1067 }
1068 } else {
1069 return false;
1070 }
1071
1072 Block *accessBlock = owner->getBlock();
1073 accesses.push_back({owner, index, accessBlock});
1074 auto [it, inserted] = elementBlocks.try_emplace(index, accessBlock);
1075 if ((!inserted && it->second != accessBlock) ||
1076 !hasStraightLineRegionPath(alloc->getBlock(), accessBlock)) {
1077 ineligibleElements.insert(index);
1078 }
1079 }
1080 for (Attribute index : ineligibleElements) {
1081 elementBlocks.erase(index);
1082 }
1083 return true;
1084}
1085
1089static void promoteStraightLineStaticArrays(ModuleOp module) {
1090 using ElementKey = std::pair<Value, Attribute>;
1091 DenseMap<Block *, DenseMap<Operation *, ElementKey>> accessesByBlock;
1092 SmallVector<CreateArrayOp> allocations;
1093 size_t eligibleElementCount = 0;
1094 module.walk([&](CreateArrayOp alloc) {
1095 DenseMap<Attribute, Block *> elementBlocks;
1096 SmallVector<StraightLineStaticAccess> accesses;
1097 if (!collectStraightLineStaticElements(alloc, elementBlocks, accesses)) {
1098 return;
1099 }
1100 allocations.push_back(alloc);
1101 eligibleElementCount += elementBlocks.size();
1102 for (const StraightLineStaticAccess &access : accesses) {
1103 if (elementBlocks.contains(access.index)) {
1104 accessesByBlock[access.block].try_emplace(access.op, alloc.getResult(), access.index);
1105 }
1106 }
1107 });
1108 LLVM_DEBUG(
1109 llvm::dbgs() << "Straight-line static array promotion: " << eligibleElementCount
1110 << " elements eligible\n";
1111 );
1112
1113 for (auto &[block, eligibleAccesses] : accessesByBlock) {
1114 DenseMap<ElementKey, Value> latestValues;
1115
1116 for (Operation &op : llvm::make_early_inc_range(*block)) {
1117 auto accessIt = eligibleAccesses.find(&op);
1118 if (accessIt == eligibleAccesses.end()) {
1119 continue;
1120 }
1121 auto [array, index] = accessIt->second;
1122
1123 if (auto read = dyn_cast<ReadArrayOp>(&op)) {
1124 Value &latest = latestValues[{array, index}];
1125 if (!latest) {
1126 OpBuilder builder(read);
1127 latest = llzk::NonDetOp::create(builder, read.getLoc(), read.getType());
1128 }
1129 read.getResult().replaceAllUsesWith(latest);
1130 read.erase();
1131 continue;
1132 }
1133
1134 if (auto write = dyn_cast<WriteArrayOp>(&op)) {
1135 latestValues[{array, index}] = write.getRvalue();
1136 write.erase();
1137 }
1138 }
1139 }
1140
1141 for (CreateArrayOp alloc : allocations) {
1142 if (alloc.getResult().use_empty()) {
1143 alloc.erase();
1144 }
1145 }
1146}
1147
1148class StraightLineStaticArrayPromotionPassImpl
1150 StraightLineStaticArrayPromotionPassImpl> {
1151public:
1152 void runOnOperation() override { promoteStraightLineStaticArrays(getOperation()); }
1153};
1154
1156class PassImpl : public llzk::array::impl::ArrayToScalarPassBase<PassImpl> {
1157 using Base = ArrayToScalarPassBase<PassImpl>;
1158 using Base::Base;
1159
1161 static OpPassManager createPostScalarizationPipeline() {
1162 OpPassManager pm(ModuleOp::getOperationName());
1163 // Promote simple arrays directly, avoiding both SROA's temporary allocations and mem2reg's
1164 // per-slot block scans.
1166 // Use SROA (Destructurable* interfaces) to split each array with linear size `N` into `N`
1167 // arrays of size 1. This is necessary because the mem2reg pass cannot deal with indexing
1168 // and splitting up memory, i.e., it can only convert scalar memory access into SSA values.
1170 // The mem2reg pass converts all of the size-1 array allocation and access into SSA values.
1172 // Cleanup allocations made dead by memory promotion.
1174 RemoveUnusedDiscardableAllocationsPassOptions {
1175 .allocatorOpName = CreateArrayOp::getOperationName().str()
1176 }
1177 ));
1178 // Fold and remove local SSA values made dead by array promotion. Avoid the global
1179 // remove-dead-values dataflow analysis here: static array lowering can produce very large,
1180 // straight-line functions, and the targeted allocation cleanup above has already removed the
1181 // memory state that required whole-region reasoning. Consequently, this pass no longer prunes
1182 // dead function arguments/results or loop iteration values that canonicalization cannot remove.
1183 pm.addPass(createCanonicalizerPass());
1184 return pm;
1185 }
1186
1187 void getDependentDialects(DialectRegistry &registry) const override {
1188 auto nestedPM = createPostScalarizationPipeline();
1189 nestedPM.getDependentDialects(registry);
1190 }
1191
1192 void runOnOperation() override {
1193 ModuleOp module = getOperation();
1194
1195 if (failed(step0(module))) {
1196 return signalPassFailure();
1197 }
1198 LLVM_DEBUG({
1199 llvm::dbgs() << "After step 0:\n";
1200 module.dump();
1201 });
1202
1203 {
1204 // This is divided into 2 steps to simplify the implementation for member-related ops. The
1205 // issue is that the conversions for member read/write expect the mapping of array index to
1206 // member name+type to already be populated for the referenced member (although this could be
1207 // computed on demand if desired but it complicates the implementation a bit).
1208 SymbolTableCollection symTables;
1209 MemberReplacementMap memberRepMap;
1210 if (failed(step1(module, symTables, memberRepMap))) {
1211 return signalPassFailure();
1212 }
1213 LLVM_DEBUG({
1214 llvm::dbgs() << "After step 1:\n";
1215 module.dump();
1216 });
1217
1218 if (failed(step2(module, symTables, memberRepMap))) {
1219 return signalPassFailure();
1220 }
1221 LLVM_DEBUG({
1222 llvm::dbgs() << "After step 2:\n";
1223 module.dump();
1224 });
1225 }
1226
1227 step3(module);
1228 LLVM_DEBUG({
1229 llvm::dbgs() << "After step 3:\n";
1230 module.dump();
1231 });
1232
1233 foldConstantGlobalArrayReads(module);
1234
1235 OpPassManager nestedPM = createPostScalarizationPipeline();
1236 if (failed(runPipeline(nestedPM, module))) {
1237 signalPassFailure();
1238 return;
1239 }
1240 LLVM_DEBUG({
1241 llvm::dbgs() << "After SROA+Mem2Reg pipeline:\n";
1242 module.dump();
1243 });
1244 }
1245};
1246
1247} // namespace
Apache License January AND DISTRIBUTION Definitions License shall mean the terms and conditions for and distribution as defined by Sections through of this document Licensor shall mean the copyright owner or entity authorized by the copyright owner that is granting the License Legal Entity shall mean the union of the acting entity and all other entities that control are controlled by or are under common control with that entity For the purposes of this definition control direct or to cause the direction or management of such whether by contract or including but not limited to software source documentation and configuration files Object form shall mean any form resulting from mechanical transformation or translation of a Source including but not limited to compiled object generated and conversions to other media types Work shall mean the work of whether in Source or Object made available under the as indicated by a copyright notice that is included in or attached to the whether in Source or Object that is based or other modifications as a an original work of authorship For the purposes of this Derivative Works shall not include works that remain separable or merely the Work and Derivative Works thereof Contribution shall mean any work of including the original version of the Work and any modifications or additions to that Work or Derivative Works that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner For the purposes of this definition
Definition LICENSE.txt:53
Apache License January AND DISTRIBUTION Definitions License shall mean the terms and conditions for use
Definition LICENSE.txt:9
Provides SpecializedSROA<AllocOpTy> and SpecializedMem2Reg<AllocOpTy>: pass templates that replicate ...
Common LLZK attributes.
Definition Dialect.h.inc:13
static NonDetOp create(::mlir::OpBuilder &builder, ::mlir::Location location, ::mlir::Type res)
Definition Ops.cpp.inc:94
Common implementation for handling MemberWriteOp and MemberReadOp while destructuring an aggregate ty...
static void genWrite(::mlir::OpBuilder &bldr, ::mlir::Location loc, ::mlir::Value arrayRef, ::mlir::ValueRange indices, ::mlir::Value value)
Create an array.write or array.insert for one concrete element or subarray.
::mlir::Value genRead(::mlir::OpBuilder &bldr, ::mlir::Location loc, ::mlir::Value arrayRef, ::mlir::ValueRange indices)
Create an array.read or array.extract for one concrete element or subarray.
::mlir::ArrayAttr indexOperandsToAttributeArray()
Returns the multi-dimensional indices of the array access as an Attribute array or a null pointer if ...
Definition Ops.cpp:216
LLZK array operations.
Definition Dialect.h.inc:13
static ArrayIndexGen from(ArrayType)
Construct new ArrayIndexGen. Will assert if hasStaticShape() is false.
std::optional< llvm::SmallVector< mlir::Value > > delinearize(int64_t, mlir::Location, mlir::OpBuilder &) const
inline ::llzk::array::ArrayType getArrRefType()
Gets the type of the referenced base array.
Definition Ops.h.inc:387
::mlir::TypedValue<::mlir::IndexType > getDim()
Definition Ops.h.inc:322
std::optional<::llvm::SmallVector<::mlir::ArrayAttr > > getSubelementIndices() const
Return a list of all valid indices for this ArrayType.
Definition Types.cpp:113
::mlir::Type getElementType() const
::llvm::ArrayRef<::mlir::Attribute > getDimensionSizes() const
static CreateArrayOp create(::mlir::OpBuilder &builder, ::mlir::Location location, ::llzk::array::ArrayType result, ::mlir::ValueRange elements={})
Definition Ops.cpp.inc:718
::mlir::TypedValue<::llzk::array::ArrayType > getResult()
Definition Ops.h.inc:612
static constexpr ::llvm::StringLiteral getOperationName()
Definition Ops.h.inc:581
::mlir::MutableOperandRange getElementsMutable()
Definition Ops.cpp.inc:489
::mlir::Operation::operand_range getElements()
Definition Ops.h.inc:592
::mlir::TypedValue<::llzk::array::ArrayType > getResult()
Definition Ops.h.inc:840
::mlir::TypedValue<::llzk::array::ArrayType > getRvalue()
Definition Ops.h.inc:1024
::mlir::TypedValue<::mlir::Type > getResult()
Definition Ops.h.inc:1222
::mlir::TypedValue<::llzk::array::ArrayType > getArrRef()
Definition Ops.h.inc:1198
static WriteArrayOp create(::mlir::OpBuilder &builder, ::mlir::Location location, ::mlir::Value arr_ref, ::mlir::ValueRange indices, ::mlir::Value rvalue)
Definition Ops.cpp.inc:2214
LLZK boolean operations.
Definition Dialect.h.inc:13
LLZK type conversion operations.
Definition Dialect.h.inc:13
bool hasPublicAttr()
Returns whether this member is a public output.
Definition Ops.h.inc:620
void setPublicAttr(bool newValue=true)
Adds or removes the unit llzk.pub attribute according to newValue.
Definition Ops.cpp:566
::mlir::StringAttr getSymNameAttr()
Definition Ops.h.inc:535
static MemberDefOp create(::mlir::OpBuilder &builder, ::mlir::Location location, ::mlir::StringAttr sym_name, ::mlir::TypeAttr type, bool isSignal=false, bool isColumn=false)
Definition Ops.cpp.inc:677
static MemberReadOp create(::mlir::OpBuilder &builder, ::mlir::Location location, ::mlir::Type resultType, ::mlir::Value component, ::mlir::StringAttr member)
Definition Ops.cpp.inc:1159
static MemberWriteOp create(::mlir::OpBuilder &builder, ::mlir::Location location, ::mlir::Value component, ::mlir::FlatSymbolRefAttr member_name, ::mlir::Value val)
Definition Ops.cpp.inc:1551
::mlir::TypedValue<::mlir::Type > getVal()
Definition Ops.h.inc:1141
LLZK component/struct operations.
Definition Dialect.h.inc:13
LLZK constraint emission operations.
Definition Dialect.h.inc:13
static FeltConstantOp create(::mlir::OpBuilder &builder, ::mlir::Location location, ::mlir::Type result, ::llzk::felt::FeltConstAttr value)
Definition Ops.cpp.inc:1274
LLZK felt (Field ELemenT) type and operations.
Definition Dialect.h.inc:13
::llvm::SmallVector< RangeT > getMapOperands()
Definition Ops.h.inc:314
::mlir::MutableOperandRange getArgOperandsMutable()
Definition Ops.cpp.inc:239
::mlir::Operation::operand_range getArgOperands()
Definition Ops.h.inc:430
CallOpAdaptor Adaptor
Definition Ops.h.inc:344
::llvm::ArrayRef<::mlir::Type > getArgumentTypes()
Required by FunctionOpInterface.
Definition Ops.h.inc:1067
::llvm::ArrayRef<::mlir::Type > getResultTypes()
Required by FunctionOpInterface.
Definition Ops.h.inc:1071
LLZK function operations.
Definition Dialect.h.inc:13
::mlir::MutableOperandRange getOperandsMutable()
Definition Ops.cpp.inc:1618
::mlir::Operation::operand_range getOperands()
Definition Ops.h.inc:1228
LLZK support for global values, defined outside of structs.
Definition Dialect.h.inc:13
LLZK include operations.
Definition Dialect.h.inc:13
Dialect for working with plain-old-data structs.
Definition Dialect.h.inc:13
LLZK types and operations to support templated/parameterized structs.
Definition Dialect.h.inc:13
static UnifiableCastOp create(::mlir::OpBuilder &builder, ::mlir::Location location, ::mlir::Type result, ::mlir::Value input)
Definition Ops.cpp.inc:1908
LLZK support for random access memory during witness generation.
Definition Dialect.h.inc:13
static LitStringOp create(::mlir::OpBuilder &builder, ::mlir::Location location, ::mlir::Type result, ::mlir::StringAttr value)
Definition Ops.cpp.inc:174
LLZK string type and operations.
Definition Dialect.h.inc:13
std::unique_ptr<::mlir::Pass > createStraightLineStaticArrayPromotionPass()
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
OpClass replaceOpWithNewOp(Rewriter &rewriter, mlir::Operation *op, Args &&...args)
Wrapper for RewriterBase::replaceOpWithNewOp() that automatically copies discardable attributes (i....
std::unique_ptr<::mlir::Pass > createRemoveUnusedDiscardableAllocationsPass()
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...
constexpr T checkedCast(U u) noexcept
Definition Compare.h:94
OpTy preserveDiscardableAttrs(mlir::Operation *src, OpTy dst)
Copy discardable attributes from src to dst.
std::unique_ptr< SpecializedMem2Reg< AllocOpTy > > createSpecializedMem2RegPass()
int64_t fromAPInt(const llvm::APInt &i)
Extract a verified MLIR index value; malformed internal IR is a fatal error.
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::unique_ptr< SpecializedSROA< AllocOpTy > > createSpecializedSROAPass()
llvm::SmallVector< std::optional< llvm::StringRef > > originalNames
llvm::SmallVector< llvm::StringRef > existingNames
llvm::SmallVector< llvm::SmallVector< std::string > > splitNameSuffixes