LLZK 0.1.0
An open-source IR for Zero Knowledge (ZK) circuits
Loading...
Searching...
No Matches
LLZKInlineStructsPass.cpp
Go to the documentation of this file.
1//===-- LLZKInlineStructsPass.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//===----------------------------------------------------------------------===//
19//===----------------------------------------------------------------------===//
20
30#include "llzk/Util/Debug.h"
33
34#include <mlir/IR/BuiltinOps.h>
35#include <mlir/Transforms/InliningUtils.h>
36#include <mlir/Transforms/WalkPatternRewriteDriver.h>
37
38#include <llvm/ADT/PostOrderIterator.h>
39#include <llvm/ADT/SmallPtrSet.h>
40#include <llvm/ADT/SmallVector.h>
41#include <llvm/ADT/StringMap.h>
42#include <llvm/ADT/TypeSwitch.h>
43#include <llvm/Support/Debug.h>
44
45#include <concepts>
46
47// Include the generated base pass class definitions.
48namespace llzk {
49// the *DECL* macro is required when a pass has options to declare the option struct
50#define GEN_PASS_DECL_INLINESTRUCTSPASS
51#define GEN_PASS_DEF_INLINESTRUCTSPASS
53} // namespace llzk
54
55using namespace mlir;
56using namespace llzk;
57using namespace llzk::component;
58using namespace llzk::function;
59
60#define DEBUG_TYPE "llzk-inline-structs"
61
62namespace {
63
64using DestMemberWithSrcStructType = MemberDefOp;
65using DestCloneOfSrcStructMember = MemberDefOp;
69using SrcStructMemberToCloneInDest = std::map<StringRef, DestCloneOfSrcStructMember>;
72using DestToSrcToClonedSrcInDest =
73 DenseMap<DestMemberWithSrcStructType, SrcStructMemberToCloneInDest>;
74
77static inline Value getSelfValue(FuncDefOp f) {
78 if (f.nameIsCompute()) {
79 return f.getSelfValueFromCompute();
80 } else if (f.nameIsConstrain()) {
82 } else {
83 llvm_unreachable("expected \"@compute\" or \"@constrain\" function");
84 }
85}
86
89static inline MemberDefOp getDef(SymbolTableCollection &tables, MemberRefOpInterface fRef) {
90 auto r = fRef.getMemberDefOp(tables);
91 assert(succeeded(r));
92 return r->get();
93}
94
97static FailureOr<MemberWriteOp>
98findOpThatStoresSubcmp(Value writtenValue, function_ref<InFlightDiagnostic()> emitError) {
99 MemberWriteOp foundWrite = nullptr;
100 for (Operation *user : writtenValue.getUsers()) {
101 if (MemberWriteOp writeOp = llvm::dyn_cast<MemberWriteOp>(user)) {
102 // Find the write op that stores the created value
103 if (writeOp.getVal() == writtenValue) {
104 if (foundWrite) {
105 // Note: There is no reason for a subcomponent to be stored to more than one member.
106 auto diag = emitError().append("result should not be written to more than one member.");
107 diag.attachNote(foundWrite.getLoc()).append("written here");
108 diag.attachNote(writeOp.getLoc()).append("written here");
109 return diag;
110 } else {
111 foundWrite = writeOp;
112 }
113 }
114 }
115 }
116 if (!foundWrite) {
117 // Note: There is no reason to construct a subcomponent and not store it to a member.
118 return emitError().append("result should be written to a member.");
119 }
120 return foundWrite;
121}
122
126static bool combineHelper(
127 MemberReadOp readOp, SymbolTableCollection &tables,
128 const DestToSrcToClonedSrcInDest &destToSrcToClone, MemberRefOpInterface destMemberRefOp
129) {
130 LLVM_DEBUG({
131 llvm::dbgs() << "[combineHelper] " << readOp << " => " << destMemberRefOp << '\n';
132 });
133
134 auto srcToClone = destToSrcToClone.find(getDef(tables, destMemberRefOp));
135 if (srcToClone == destToSrcToClone.end()) {
136 return false;
137 }
138 SrcStructMemberToCloneInDest oldToNewMembers = srcToClone->second;
139 auto resNewMember = oldToNewMembers.find(readOp.getMemberName());
140 if (resNewMember == oldToNewMembers.end()) {
141 return false;
142 }
143
144 // Replace this MemberReadOp with a new one that targets the cloned member.
145 OpBuilder builder(readOp);
146 MemberReadOp newRead = builder.create<MemberReadOp>(
147 readOp.getLoc(), readOp.getType(), destMemberRefOp.getComponent(),
148 resNewMember->second.getNameAttr()
149 );
150 readOp.replaceAllUsesWith(newRead.getOperation());
151 readOp.erase(); // delete the original MemberReadOp
152 return true;
153}
154
168static bool combineReadChain(
169 MemberReadOp readOp, SymbolTableCollection &tables,
170 const DestToSrcToClonedSrcInDest &destToSrcToClone
171) {
172 LLVM_DEBUG({ llvm::dbgs() << "[combineReadChain] " << readOp << '\n'; });
173
174 MemberReadOp readThatDefinesBaseComponent =
175 llvm::dyn_cast_if_present<MemberReadOp>(readOp.getComponent().getDefiningOp());
176 if (!readThatDefinesBaseComponent) {
177 return false;
179 return combineHelper(readOp, tables, destToSrcToClone, readThatDefinesBaseComponent);
180}
181
186///
187/// Example:
188/// Given the mapping (@fa, !struct.type<@Component10A>) -> @f -> \@"fa:!s<@Component10A>+f"
189/// And the input:
190/// %0 = struct.new : !struct.type<@Main>
191/// %2 = struct.new : !struct.type<@Component10A>
192/// struct.writem %0[@fa] = %2 : !struct.type<@Main>, !struct.type<@Component10A>
193/// %4 = struct.readm %2[@f] : !struct.type<@Component10A>, !felt.type
194/// Replace the final read with:
195/// %4 = struct.readm %0[@"fa:!s<@Component10A>+f"] : !struct.type<@Main>, !felt.type
196///
197/// Return true if replaced, false if not.
198static LogicalResult combineNewThenReadChain(
199 MemberReadOp readOp, SymbolTableCollection &tables,
200 const DestToSrcToClonedSrcInDest &destToSrcToClone
201) {
202 LLVM_DEBUG({ llvm::dbgs() << "[combineNewThenReadChain] " << readOp << '\n'; });
204 CreateStructOp createThatDefinesBaseComponent =
205 llvm::dyn_cast_if_present<CreateStructOp>(readOp.getComponent().getDefiningOp());
206 if (!createThatDefinesBaseComponent) {
207 return success(); // No error. The pattern simply doesn't match.
208 }
209 FailureOr<MemberWriteOp> foundWrite =
210 findOpThatStoresSubcmp(createThatDefinesBaseComponent, [&createThatDefinesBaseComponent]() {
211 return createThatDefinesBaseComponent.emitOpError();
212 });
213 if (failed(foundWrite)) {
214 return failure(); // error already printed within findOpThatStoresSubcmp()
215 }
216 return success(combineHelper(readOp, tables, destToSrcToClone, foundWrite.value()));
218
219static inline MemberReadOp getMemberReadThatDefinesSelfValuePassedToConstrain(CallOp callOp) {
220 Value selfArgFromCall = callOp.getSelfValueFromConstrain();
221 return llvm::dyn_cast_if_present<MemberReadOp>(selfArgFromCall.getDefiningOp());
223
226struct PendingErasure {
227 SmallPtrSet<Operation *, 8> memberReadOps;
228 SmallPtrSet<Operation *, 8> memberWriteOps;
229 SmallVector<CreateStructOp> newStructOps;
230 SmallVector<DestMemberWithSrcStructType> memberDefs;
231};
232
234class StructInliner {
235 SymbolTableCollection &tables;
236 PendingErasure &toDelete;
237
238 StructDefOp srcStruct;
240 StructDefOp destStruct;
241
242 inline MemberDefOp getDef(MemberRefOpInterface fRef) const { return ::getDef(tables, fRef); }
244 // Update member read/write ops that target the "self" value of the FuncDefOp plus some key in
245 // `oldToNewMemberDef` to instead target the new base Value provided to the constructor plus the
246 // mapped Value from `oldToNewMemberDef`.
247 // Example:
248 // old: %1 = struct.readm %0[@f1] : <@Component1A>, !felt.type
249 // new: %1 = struct.readm %self[@"f2:!s<@Component1A>+f1"] : <@Component1B>, !felt.type
250 class MemberRefRewriter final : public OpInterfaceRewritePattern<MemberRefOpInterface> {
253 FuncDefOp funcRef;
255 Value oldBaseVal;
257 Value newBaseVal;
258 const SrcStructMemberToCloneInDest &oldToNewMembers;
259
260 public:
261 MemberRefRewriter(
262 FuncDefOp originalFunc, Value newRefBase,
263 const SrcStructMemberToCloneInDest &oldToNewMemberDef
264 )
265 : OpInterfaceRewritePattern(originalFunc.getContext()), funcRef(originalFunc),
266 oldBaseVal(nullptr), newBaseVal(newRefBase), oldToNewMembers(oldToNewMemberDef) {}
267
268 LogicalResult match(MemberRefOpInterface op) const final {
269 assert(oldBaseVal); // ensure it's used via `cloneWithMemberRefUpdate()` only
270 // Check if the MemberRef accesses a member of "self" within the `oldToNewMembers` map.
271 // Per `cloneWithMemberRefUpdate()`, `oldBaseVal` is the "self" value of `funcRef` so
272 // check for a match there and then check that the referenced member name is in the map.
273 return success(
274 op.getComponent() == oldBaseVal && oldToNewMembers.contains(op.getMemberName())
275 );
276 }
277
278 void rewrite(MemberRefOpInterface op, PatternRewriter &rewriter) const final {
279 rewriter.modifyOpInPlace(op, [this, &op]() {
280 DestCloneOfSrcStructMember newF = oldToNewMembers.at(op.getMemberName());
281 op.setMemberName(newF.getSymName());
282 op.getComponentMutable().set(this->newBaseVal);
283 });
284 }
285
288 static FuncDefOp cloneWithMemberRefUpdate(std::unique_ptr<MemberRefRewriter> thisPat) {
289 IRMapping mapper;
290 FuncDefOp srcFuncClone = thisPat->funcRef.clone(mapper);
291 // Update some data in the `MemberRefRewriter` instance before moving it.
292 thisPat->funcRef = srcFuncClone;
293 thisPat->oldBaseVal = getSelfValue(srcFuncClone);
294 // Run the rewriter to replace read/write ops
295 MLIRContext *ctx = thisPat->getContext();
296 RewritePatternSet patterns(ctx, std::move(thisPat));
297 walkAndApplyPatterns(srcFuncClone, std::move(patterns));
298
299 return srcFuncClone;
300 }
301 };
302
304 class ImplBase {
305 protected:
306 const StructInliner &data;
307 const DestToSrcToClonedSrcInDest &destToSrcToClone;
308
311 virtual MemberRefOpInterface getSelfRefMember(CallOp callOp) = 0;
312 virtual void processCloneBeforeInlining(FuncDefOp func) {}
313 virtual ~ImplBase() = default;
314
315 public:
316 ImplBase(const StructInliner &inliner, const DestToSrcToClonedSrcInDest &destToSrcToCloneRef)
317 : data(inliner), destToSrcToClone(destToSrcToCloneRef) {}
318
319 LogicalResult doInlining(FuncDefOp srcFunc, FuncDefOp destFunc) {
320 LLVM_DEBUG({
321 llvm::dbgs() << "[doInlining] SOURCE FUNCTION:\n";
322 srcFunc.dump();
323 llvm::dbgs() << "[doInlining] DESTINATION FUNCTION:\n";
324 destFunc.dump();
325 });
326
327 InlinerInterface inliner(destFunc.getContext());
328
330 auto callHandler = [this, &inliner, &srcFunc](CallOp callOp) {
331 // Ensure the CallOp targets `srcFunc`
332 auto callOpTarget = callOp.getCalleeTarget(this->data.tables);
333 assert(succeeded(callOpTarget));
334 if (callOpTarget->get() != srcFunc) {
335 return WalkResult::advance();
336 }
337
338 // Get the "self" struct parameter from the CallOp and determine which member that struct
339 // was stored in within the caller (i.e. `destFunc`).
340 MemberRefOpInterface selfMemberRefOp = this->getSelfRefMember(callOp);
341 if (!selfMemberRefOp) {
342 // Note: error message was already printed within `getSelfRefMember()`
343 return WalkResult::interrupt(); // use interrupt to signal failure
344 }
345
346 // Create a clone of the source function (must do the whole function not just the body
347 // region because `inlineCall()` expects the Region to have a parent op) and update member
348 // references to the old struct members to instead use the new struct members.
349 FuncDefOp srcFuncClone = MemberRefRewriter::cloneWithMemberRefUpdate(
350 std::make_unique<MemberRefRewriter>(
351 srcFunc, selfMemberRefOp.getComponent(),
352 this->destToSrcToClone.at(this->data.getDef(selfMemberRefOp))
353 )
354 );
355 this->processCloneBeforeInlining(srcFuncClone);
356
357 // Inline the cloned function in place of `callOp`
358 LogicalResult inlineCallRes =
359 inlineCall(inliner, callOp, srcFuncClone, &srcFuncClone.getBody(), false);
360 if (failed(inlineCallRes)) {
361 callOp.emitError().append("Failed to inline ", srcFunc.getFullyQualifiedName()).report();
362 return WalkResult::interrupt(); // use interrupt to signal failure
363 }
364 srcFuncClone.erase(); // delete what's left after transferring the body elsewhere
365 callOp.erase(); // delete the original CallOp
366 return WalkResult::skip(); // Must skip because the CallOp was erased.
367 };
368
369 auto memberWriteHandler = [this](MemberWriteOp writeOp) {
370 // Check if the member ref op should be deleted in the end
371 if (this->destToSrcToClone.contains(this->data.getDef(writeOp))) {
372 this->data.toDelete.memberWriteOps.insert(writeOp);
373 }
374 return WalkResult::advance();
375 };
376
379 auto memberReadHandler = [this](MemberReadOp readOp) {
380 // Check if the member ref op should be deleted in the end
381 if (this->destToSrcToClone.contains(this->data.getDef(readOp))) {
382 this->data.toDelete.memberReadOps.insert(readOp);
383 }
384 // If the MemberReadOp was replaced/erased, must skip.
385 return combineReadChain(readOp, this->data.tables, destToSrcToClone)
386 ? WalkResult::skip()
387 : WalkResult::advance();
388 };
389
390 WalkResult walkRes = destFunc.getBody().walk<WalkOrder::PreOrder>([&](Operation *op) {
391 return TypeSwitch<Operation *, WalkResult>(op)
392 .Case<CallOp>(callHandler)
393 .Case<MemberWriteOp>(memberWriteHandler)
394 .Case<MemberReadOp>(memberReadHandler)
395 .Default([](Operation *) { return WalkResult::advance(); });
396 });
397
398 return failure(walkRes.wasInterrupted());
399 }
400 };
401
402 class ConstrainImpl : public ImplBase {
403 using ImplBase::ImplBase;
404
405 MemberRefOpInterface getSelfRefMember(CallOp callOp) override {
406 LLVM_DEBUG({ llvm::dbgs() << "[ConstrainImpl::getSelfRefMember] " << callOp << '\n'; });
407
408 // The typical pattern is to read a struct instance from a member and then call "constrain()"
409 // on it. Get the Value passed as the "self" struct to the CallOp and determine which member
410 // it was read from in the current struct (i.e., `destStruct`).
411 MemberRefOpInterface selfMemberRef =
412 getMemberReadThatDefinesSelfValuePassedToConstrain(callOp);
413 if (selfMemberRef &&
414 selfMemberRef.getComponent().getType() == this->data.destStruct.getType()) {
415 return selfMemberRef;
416 }
417 callOp.emitError()
418 .append(
419 "expected \"self\" parameter to \"@", FUNC_NAME_CONSTRAIN,
420 "\" to be passed a value read from a member in the current stuct."
421 )
422 .report();
423 return nullptr;
424 }
425 };
426
427 class ComputeImpl : public ImplBase {
428 using ImplBase::ImplBase;
429
430 MemberRefOpInterface getSelfRefMember(CallOp callOp) override {
431 LLVM_DEBUG({ llvm::dbgs() << "[ComputeImpl::getSelfRefMember] " << callOp << '\n'; });
432
433 // The typical pattern is to write the return value of "compute()" to a member in
434 // the current struct (i.e., `destStruct`).
435 // It doesn't really make sense (although there is no semantic restriction against it) to just
436 // pass the "compute()" result into another function and never write it to a member since that
437 // leaves no way for the "constrain()" function to call "constrain()" on that result struct.
438 FailureOr<MemberWriteOp> foundWrite =
439 findOpThatStoresSubcmp(callOp.getSelfValueFromCompute(), [&callOp]() {
440 return callOp.emitOpError().append("\"@", FUNC_NAME_COMPUTE, "\" ");
441 });
442 return static_cast<MemberRefOpInterface>(foundWrite.value_or(nullptr));
443 }
444
445 void processCloneBeforeInlining(FuncDefOp func) override {
446 // Within the compute function, find `CreateStructOp` with `srcStruct` type and mark them
447 // for later deletion. The deletion must occur later because these values may still have
448 // uses until ALL callees of a function have been inlined.
449 func.getBody().walk([this](CreateStructOp newStructOp) {
450 if (newStructOp.getType() == this->data.srcStruct.getType()) {
451 this->data.toDelete.newStructOps.push_back(newStructOp);
452 }
453 });
454 }
455 };
456
457 // Find any member(s) in `destStruct` whose type matches `srcStruct` (allowing any parameters, if
458 // applicable). For each such member, clone all members from `srcStruct` into `destStruct` and
459 // cache the mapping of `destStruct` to `srcStruct` to cloned members in the return value.
460 DestToSrcToClonedSrcInDest cloneMembers() {
461 DestToSrcToClonedSrcInDest destToSrcToClone;
462
463 SymbolTable &destStructSymTable = tables.getSymbolTable(destStruct);
464 StructType srcStructType = srcStruct.getType();
465 for (MemberDefOp destMember : destStruct.getMemberDefs()) {
466 if (StructType destMemberType = llvm::dyn_cast<StructType>(destMember.getType())) {
467 UnificationMap unifications;
468 if (!structTypesUnify(srcStructType, destMemberType, {}, &unifications)) {
469 continue;
470 }
471 assert(unifications.empty()); // `makePlan()` reports failure earlier
472 // Mark the original `destMember` for deletion
473 toDelete.memberDefs.push_back(destMember);
474 // Clone each member from 'srcStruct' into 'destStruct'. Add an entry to `destToSrcToClone`
475 // even if there are no members in `srcStruct` so its presence can be used as a marker.
476 SrcStructMemberToCloneInDest &srcToClone = destToSrcToClone[destMember];
477 std::vector<MemberDefOp> srcMembers = srcStruct.getMemberDefs();
478 if (srcMembers.empty()) {
479 continue;
480 }
481 OpBuilder builder(destMember);
482 std::string newNameBase =
483 destMember.getName().str() + ':' + BuildShortTypeString::from(destMemberType);
484 for (MemberDefOp srcMember : srcMembers) {
485 DestCloneOfSrcStructMember newF = llvm::cast<MemberDefOp>(builder.clone(*srcMember));
486 newF.setName(builder.getStringAttr(newNameBase + '+' + newF.getName()));
487 srcToClone[srcMember.getSymNameAttr()] = newF;
488 // Also update the cached SymbolTable
489 destStructSymTable.insert(newF);
490 }
491 }
492 }
493 return destToSrcToClone;
494 }
495
497 inline LogicalResult inlineConstrainCall(const DestToSrcToClonedSrcInDest &destToSrcToClone) {
498 return ConstrainImpl(*this, destToSrcToClone)
499 .doInlining(srcStruct.getConstrainFuncOp(), destStruct.getConstrainFuncOp());
500 }
501
503 inline LogicalResult inlineComputeCall(const DestToSrcToClonedSrcInDest &destToSrcToClone) {
504 return ComputeImpl(*this, destToSrcToClone)
505 .doInlining(srcStruct.getComputeFuncOp(), destStruct.getComputeFuncOp());
506 }
507
508public:
509 StructInliner(
510 SymbolTableCollection &tbls, PendingErasure &opsToDelete, StructDefOp from, StructDefOp into
511 )
512 : tables(tbls), toDelete(opsToDelete), srcStruct(from), destStruct(into) {}
513
514 FailureOr<DestToSrcToClonedSrcInDest> doInline() {
515 LLVM_DEBUG(
516 llvm::dbgs() << "[StructInliner] merge " << srcStruct.getSymNameAttr() << " into "
517 << destStruct.getSymNameAttr() << '\n'
518 );
519
520 DestToSrcToClonedSrcInDest destToSrcToClone = cloneMembers();
521 if (failed(inlineConstrainCall(destToSrcToClone)) ||
522 failed(inlineComputeCall(destToSrcToClone))) {
523 return failure(); // error already printed within doInlining()
524 }
525 return destToSrcToClone;
526 }
527};
528
529template <typename T>
530concept HasContainsOp = requires(const T &t, Operation *p) {
531 { t.contains(p) } -> std::convertible_to<bool>;
532};
533
535template <typename... PendingDeletionSets>
537class DanglingUseHandler {
538 SymbolTableCollection &tables;
539 const DestToSrcToClonedSrcInDest &destToSrcToClone;
540 std::tuple<const PendingDeletionSets &...> otherRefsToBeDeleted;
541
542public:
543 DanglingUseHandler(
544 SymbolTableCollection &symTables, const DestToSrcToClonedSrcInDest &destToSrcToCloneRef,
545 const PendingDeletionSets &...otherRefsPendingDeletion
546 )
547 : tables(symTables), destToSrcToClone(destToSrcToCloneRef),
548 otherRefsToBeDeleted(otherRefsPendingDeletion...) {}
549
555 LogicalResult handle(Operation *op) const {
556 if (op->use_empty()) {
557 return success(); // safe to erase
558 }
559
560 LLVM_DEBUG({
561 llvm::dbgs() << "[DanglingUseHandler::handle] op: " << *op << '\n';
562 llvm::dbgs() << "[DanglingUseHandler::handle] in function: "
563 << op->getParentOfType<FuncDefOp>() << '\n';
564 });
565 for (OpOperand &use : llvm::make_early_inc_range(op->getUses())) {
566 if (CallOp c = llvm::dyn_cast<CallOp>(use.getOwner())) {
567 if (failed(handleUseInCallOp(use, c, op))) {
568 return failure();
569 }
570 } else {
571 Operation *user = use.getOwner();
572 // Report an error for any user other than some member ref that will be deleted anyway.
573 if (!opWillBeDeleted(user)) {
574 return op->emitOpError()
575 .append(
576 "with use in '", user->getName().getStringRef(),
577 "' is not (currently) supported by this pass."
578 )
579 .attachNote(user->getLoc())
580 .append("used by this operation");
581 }
582 }
583 }
584 // Ensure that all users of the 'op' were deleted above, or will be per 'otherRefsToBeDeleted'.
585 if (!op->use_empty()) {
586 for (Operation *user : op->getUsers()) {
587 if (!opWillBeDeleted(user)) {
588 llvm::errs() << "Op has remaining use(s) that could not be removed: " << *op << '\n';
589 llvm_unreachable("Expected all uses to be removed");
590 }
591 }
592 }
593 return success();
594 }
595
596private:
597 inline LogicalResult handleUseInCallOp(OpOperand &use, CallOp inCall, Operation *origin) const {
598 LLVM_DEBUG(
599 llvm::dbgs() << "[DanglingUseHandler::handleUseInCallOp] use in call: " << inCall << '\n'
600 );
601 unsigned argIdx = use.getOperandNumber() - inCall.getArgOperands().getBeginOperandIndex();
602 LLVM_DEBUG(
603 llvm::dbgs() << "[DanglingUseHandler::handleUseInCallOp] at index: " << argIdx << '\n'
604 );
605
606 auto tgtFuncRes = inCall.getCalleeTarget(tables);
607 if (failed(tgtFuncRes)) {
608 return origin
609 ->emitOpError("as argument to an unknown function is not supported by this pass.")
610 .attachNote(inCall.getLoc())
611 .append("used by this call");
612 }
613 FuncDefOp tgtFunc = tgtFuncRes->get();
614 LLVM_DEBUG(
615 llvm::dbgs() << "[DanglingUseHandler::handleUseInCallOp] call target: " << tgtFunc << '\n'
616 );
617 if (tgtFunc.isExternal()) {
618 // Those without a body (i.e. external implementation) present a problem because LLZK does
619 // not define a memory layout for the external implementation to interpret the struct.
620 return origin
621 ->emitOpError("as argument to a no-body free function is not supported by this pass.")
622 .attachNote(inCall.getLoc())
623 .append("used by this call");
624 }
625
626 MemberRefOpInterface paramFromMember =
627 TypeSwitch<Operation *, MemberRefOpInterface>(origin)
628 .template Case<MemberReadOp>([](auto p) { return p; })
629 .template Case<CreateStructOp>([](auto p) {
630 return findOpThatStoresSubcmp(p, [&p]() { return p.emitOpError(); }).value_or(nullptr);
631 }).Default([](Operation *p) {
632 llvm::errs() << "Encountered unexpected op: "
633 << (p ? p->getName().getStringRef() : "<<null>>") << '\n';
634 llvm_unreachable("Unexpected op kind");
635 return nullptr;
636 });
637 LLVM_DEBUG({
638 llvm::dbgs() << "[DanglingUseHandler::handleUseInCallOp] member ref op for param: "
639 << (paramFromMember ? debug::toStringOne(paramFromMember) : "<<null>>") << '\n';
640 });
641 if (!paramFromMember) {
642 return failure(); // error already printed within findOpThatStoresSubcmp()
643 }
644 const SrcStructMemberToCloneInDest &newMembers =
645 destToSrcToClone.at(getDef(tables, paramFromMember));
646 LLVM_DEBUG({
647 llvm::dbgs() << "[DanglingUseHandler::handleUseInCallOp] members to split: "
648 << debug::toStringList(newMembers) << '\n';
649 });
650
651 // Convert the FuncDefOp side first (to use the easier builder for the new CallOp).
652 splitFunctionParam(tgtFunc, argIdx, newMembers);
653 LLVM_DEBUG({
654 llvm::dbgs() << "[DanglingUseHandler::handleUseInCallOp] UPDATED call target: " << tgtFunc
655 << '\n';
656 llvm::dbgs() << "[DanglingUseHandler::handleUseInCallOp] UPDATED call target type: "
657 << tgtFunc.getFunctionType() << '\n';
658 });
659
660 // Convert the CallOp side. Add a MemberReadOp for each value from the struct and pass them
661 // individually in place of the struct parameter.
662 OpBuilder builder(inCall);
663 SmallVector<Value> splitArgs;
664 // Before the CallOp, insert a read from every new member. These Values will replace the
665 // original argument in the CallOp.
666 Value originalBaseVal = paramFromMember.getComponent();
667 for (auto [origName, newMemberRef] : newMembers) {
668 splitArgs.push_back(builder.create<MemberReadOp>(
669 inCall.getLoc(), newMemberRef.getType(), originalBaseVal, newMemberRef.getNameAttr()
670 ));
671 }
672 // Generate the new argument list from the original but replace 'argIdx'
673 SmallVector<Value> newOpArgs(inCall.getArgOperands());
674 newOpArgs.insert(
675 newOpArgs.erase(newOpArgs.begin() + argIdx), splitArgs.begin(), splitArgs.end()
676 );
677 // Create the new CallOp, replace uses of the old with the new, delete the old
678 inCall.replaceAllUsesWith(builder.create<CallOp>(
679 inCall.getLoc(), tgtFunc, CallOp::toVectorOfValueRange(inCall.getMapOperands()),
680 inCall.getNumDimsPerMapAttr(), newOpArgs
681 ));
682 inCall.erase();
683 LLVM_DEBUG({
684 llvm::dbgs() << "[DanglingUseHandler::handleUseInCallOp] UPDATED function: "
685 << origin->getParentOfType<FuncDefOp>() << '\n';
686 });
687 return success();
688 }
689
691 inline bool opWillBeDeleted(Operation *otherOp) const {
692 return std::apply([&](const auto &...sets) {
693 return ((sets.contains(otherOp)) || ...);
694 }, otherRefsToBeDeleted);
695 }
696
701 static void splitFunctionParam(
702 FuncDefOp func, unsigned paramIdx, const SrcStructMemberToCloneInDest &nameToNewMember
703 ) {
704 class Impl : public FunctionTypeConverter {
705 unsigned inputIdx;
706 const SrcStructMemberToCloneInDest &newMembers;
707
708 public:
709 Impl(unsigned paramIdx, const SrcStructMemberToCloneInDest &nameToNewMember)
710 : inputIdx(paramIdx), newMembers(nameToNewMember) {}
711
712 protected:
713 SmallVector<Type> convertInputs(ArrayRef<Type> origTypes) override {
714 SmallVector<Type> newTypes(origTypes);
715 auto it = newTypes.erase(newTypes.begin() + inputIdx);
716 for (auto [_, newMember] : newMembers) {
717 newTypes.insert(it, newMember.getType());
718 ++it;
719 }
720 return newTypes;
721 }
722 SmallVector<Type> convertResults(ArrayRef<Type> origTypes) override {
723 return SmallVector<Type>(origTypes);
724 }
725 ArrayAttr convertInputAttrs(ArrayAttr origAttrs, SmallVector<Type>) override {
726 if (origAttrs) {
727 // Replicate the value at `origAttrs[inputIdx]` to have `newMembers.size()`
728 SmallVector<Attribute> newAttrs(origAttrs.getValue());
729 newAttrs.insert(newAttrs.begin() + inputIdx, newMembers.size() - 1, origAttrs[inputIdx]);
730 return ArrayAttr::get(origAttrs.getContext(), newAttrs);
731 }
732 return nullptr;
733 }
734 ArrayAttr convertResultAttrs(ArrayAttr origAttrs, SmallVector<Type>) override {
735 return origAttrs;
736 }
737
738 void processBlockArgs(Block &entryBlock, RewriterBase &rewriter) override {
739 Value oldStructRef = entryBlock.getArgument(inputIdx);
740
741 // Insert new Block arguments, one per member, following the original one. Keep a map
742 // of member name to the associated block argument for replacing MemberReadOp.
743 llvm::StringMap<BlockArgument> memberNameToNewArg;
744 Location loc = oldStructRef.getLoc();
745 unsigned idx = inputIdx;
746 for (auto [memberName, newMember] : newMembers) {
747 // note: pre-increment so the original to be erased is still at `inputIdx`
748 BlockArgument newArg = entryBlock.insertArgument(++idx, newMember.getType(), loc);
749 memberNameToNewArg[memberName] = newArg;
750 }
751
752 // Find all member reads from the original Block argument and replace uses of those
753 // reads with the appropriate new Block argument.
754 for (OpOperand &oldBlockArgUse : llvm::make_early_inc_range(oldStructRef.getUses())) {
755 if (MemberReadOp readOp = llvm::dyn_cast<MemberReadOp>(oldBlockArgUse.getOwner())) {
756 if (readOp.getComponent() == oldStructRef) {
757 BlockArgument newArg = memberNameToNewArg.at(readOp.getMemberName());
758 rewriter.replaceAllUsesWith(readOp, newArg);
759 rewriter.eraseOp(readOp);
760 continue;
761 }
762 }
763 // Currently, there's no other way in which a StructType parameter can be used.
764 llvm::errs() << "Unexpected use of " << oldBlockArgUse.get() << " in "
765 << *oldBlockArgUse.getOwner() << '\n';
766 llvm_unreachable("Not yet implemented");
767 }
768
769 // Delete the original Block argument
770 entryBlock.eraseArgument(inputIdx);
771 }
772 };
773 IRRewriter rewriter(func.getContext());
774 Impl(paramIdx, nameToNewMember).convert(func, rewriter);
775 }
776};
777
778static LogicalResult finalizeStruct(
779 SymbolTableCollection &tables, StructDefOp caller, PendingErasure &&toDelete,
780 DestToSrcToClonedSrcInDest &&destToSrcToClone
781) {
782 LLVM_DEBUG({
783 llvm::dbgs() << "[finalizeStruct] dumping 'caller' struct before compressing chains:\n";
784 caller.print(llvm::dbgs(), OpPrintingFlags().assumeVerified());
785 llvm::dbgs() << '\n';
786 });
787
788 // Compress chains of reads that result after inlining multiple callees.
789 caller.getConstrainFuncOp().walk([&tables, &destToSrcToClone](MemberReadOp readOp) {
790 combineReadChain(readOp, tables, destToSrcToClone);
791 });
792 FuncDefOp computeFn = caller.getComputeFuncOp();
793 Value computeSelfVal = computeFn.getSelfValueFromCompute();
794 auto res = computeFn.walk([&tables, &destToSrcToClone, &computeSelfVal](MemberReadOp readOp) {
795 combineReadChain(readOp, tables, destToSrcToClone);
796 // Reads targeting the "self" value from "compute()" are not eligible for the compression
797 // provided in `combineNewThenReadChain()` and will actually cause an error within.
798 if (readOp.getComponent() == computeSelfVal) {
799 return WalkResult::advance();
800 }
801 LogicalResult innerRes = combineNewThenReadChain(readOp, tables, destToSrcToClone);
802 return failed(innerRes) ? WalkResult::interrupt() : WalkResult::advance();
803 });
804 if (res.wasInterrupted()) {
805 return failure(); // error already printed within combineNewThenReadChain()
806 }
807
808 LLVM_DEBUG({
809 llvm::dbgs() << "[finalizeStruct] dumping 'caller' struct before deleting ops:\n";
810 caller.print(llvm::dbgs(), OpPrintingFlags().assumeVerified());
811 llvm::dbgs() << '\n';
812 llvm::dbgs() << "[finalizeStruct] ops marked for deletion:\n";
813 for (Operation *op : toDelete.memberReadOps) {
814 llvm::dbgs().indent(2) << *op << '\n';
815 }
816 for (Operation *op : toDelete.memberWriteOps) {
817 llvm::dbgs().indent(2) << *op << '\n';
818 }
819 for (CreateStructOp op : toDelete.newStructOps) {
820 llvm::dbgs().indent(2) << op << '\n';
821 }
822 for (DestMemberWithSrcStructType op : toDelete.memberDefs) {
823 llvm::dbgs().indent(2) << op << '\n';
824 }
825 });
826
827 // Handle remaining uses of CreateStructOp before deleting anything because this process
828 // needs to be able to find the MemberWriteOp instances that store the result of these ops.
829 DanglingUseHandler<SmallPtrSet<Operation *, 8>, SmallPtrSet<Operation *, 8>> useHandler(
830 tables, destToSrcToClone, toDelete.memberWriteOps, toDelete.memberReadOps
831 );
832 for (CreateStructOp op : toDelete.newStructOps) {
833 if (failed(useHandler.handle(op))) {
834 return failure(); // error already printed within handle()
835 }
836 }
837 // Next, to avoid "still has uses" errors, must erase MemberWriteOp first, then MemberReadOp,
838 // before erasing the CreateStructOp or MemberDefOp.
839 for (Operation *op : toDelete.memberWriteOps) {
840 if (failed(useHandler.handle(op))) {
841 return failure(); // error already printed within handle()
842 }
843 op->erase();
844 }
845 for (Operation *op : toDelete.memberReadOps) {
846 if (failed(useHandler.handle(op))) {
847 return failure(); // error already printed within handle()
848 }
849 op->erase();
850 }
851 for (CreateStructOp op : toDelete.newStructOps) {
852 op.erase();
853 }
854 // Finally, erase MemberDefOp via SymbolTable so table itself is updated too.
855 SymbolTable &callerSymTab = tables.getSymbolTable(caller);
856 for (DestMemberWithSrcStructType op : toDelete.memberDefs) {
857 assert(op.getParentOp() == caller); // using correct SymbolTable
858 callerSymTab.erase(op);
859 }
860
861 return success();
862}
863
864} // namespace
865
866LogicalResult performInlining(SymbolTableCollection &tables, InliningPlan &plan) {
867 for (auto &[caller, callees] : plan) {
868 // Cache operations that should be deleted but must wait until all callees are processed
869 // to ensure that all uses of the values defined by these operations are replaced.
870 PendingErasure toDelete;
871 // Cache old-to-new member mappings across all callees inlined for the current struct.
872 DestToSrcToClonedSrcInDest aggregateReplacements;
873 // Inline callees/subcomponents of the current struct
874 for (StructDefOp toInline : callees) {
875 FailureOr<DestToSrcToClonedSrcInDest> res =
876 StructInliner(tables, toDelete, toInline, caller).doInline();
877 if (failed(res)) {
878 return failure();
879 }
880 // Add current member replacements to the aggregate
881 for (auto &[k, v] : res.value()) {
882 assert(!aggregateReplacements.contains(k) && "duplicate not possible");
883 aggregateReplacements[k] = std::move(v);
884 }
885 }
886 // Complete steps to finalize/cleanup the caller
887 LogicalResult finalizeResult =
888 finalizeStruct(tables, caller, std::move(toDelete), std::move(aggregateReplacements));
889 if (failed(finalizeResult)) {
890 return failure();
891 }
892 }
893 return success();
894}
895
896namespace {
897
898class InlineStructsPass : public llzk::impl::InlineStructsPassBase<InlineStructsPass> {
899 static uint64_t complexity(FuncDefOp f) {
900 uint64_t complexity = 0;
901 f.getBody().walk([&complexity](Operation *op) {
902 if (llvm::isa<felt::MulFeltOp>(op)) {
903 ++complexity;
904 } else if (auto ee = llvm::dyn_cast<constrain::EmitEqualityOp>(op)) {
905 complexity += computeEmitEqCardinality(ee.getLhs().getType());
906 } else if (auto ec = llvm::dyn_cast<constrain::EmitContainmentOp>(op)) {
907 // TODO: increment based on dimension sizes in the operands
908 // Pending update to implementation/semantics of EmitContainmentOp.
909 ++complexity;
910 }
911 });
912 return complexity;
913 }
914
915 static FailureOr<FuncDefOp>
916 getIfStructConstrain(const SymbolUseGraphNode *node, SymbolTableCollection &tables) {
917 auto lookupRes = node->lookupSymbol(tables, false);
918 assert(succeeded(lookupRes) && "graph contains node with invalid path");
919 if (FuncDefOp f = llvm::dyn_cast<FuncDefOp>(lookupRes->get())) {
920 if (f.isStructConstrain()) {
921 return f;
922 }
923 }
924 return failure();
925 }
926
929 static inline StructDefOp getParentStruct(FuncDefOp func) {
930 assert(func.isStructConstrain()); // pre-condition
931 FailureOr<StructDefOp> currentNodeParentStruct = getParentOfType<StructDefOp>(func);
932 assert(succeeded(currentNodeParentStruct)); // follows from ODS definition
933 return currentNodeParentStruct.value();
934 }
935
937 inline bool exceedsMaxComplexity(uint64_t check) {
938 return maxComplexity > 0 && check > maxComplexity;
939 }
940
943 static inline bool canInline(FuncDefOp currentFunc, FuncDefOp successorFunc) {
944 // Find CallOp for `successorFunc` within `currentFunc` and check the condition used by
945 // `ConstrainImpl::getSelfRefMember()`.
946 //
947 // Implementation Note: There is a possibility that the "self" value is not from a member read.
948 // It could be a parameter to the current/destination function or a global read. Inlining a
949 // struct stored to a global would probably require splitting up the global into multiple, one
950 // for each member in the successor/source struct. That may not be a good idea. The parameter
951 // case could be handled but it will not have a mapping in `destToSrcToClone` in
952 // `getSelfRefMember()` and new members will still need to be added. They can be prefixed with
953 // parameter index since there is no current member name to use as the unique prefix. Handling
954 // that would require refactoring the inlining process a bit.
955 WalkResult res = currentFunc.walk([](CallOp c) {
956 return getMemberReadThatDefinesSelfValuePassedToConstrain(c)
957 ? WalkResult::interrupt() // use interrupt to indicate success
958 : WalkResult::advance();
959 });
960 LLVM_DEBUG({
961 llvm::dbgs() << "[canInline] " << successorFunc.getFullyQualifiedName() << " into "
962 << currentFunc.getFullyQualifiedName() << "? " << res.wasInterrupted() << '\n';
963 });
964 return res.wasInterrupted();
965 }
966
971 inline FailureOr<InliningPlan>
972 makePlan(const SymbolUseGraph &useGraph, SymbolTableCollection &tables) {
973 LLVM_DEBUG({
974 llvm::dbgs() << "Running InlineStructsPass with max complexity ";
975 if (maxComplexity == 0) {
976 llvm::dbgs() << "unlimited";
977 } else {
978 llvm::dbgs() << maxComplexity;
979 }
980 llvm::dbgs() << '\n';
981 });
982 InliningPlan retVal;
983 DenseMap<const SymbolUseGraphNode *, uint64_t> complexityMemo;
984
985 // NOTE: The assumption that the use graph has no cycles allows `complexityMemo` to only
986 // store the result for relevant nodes and assume nodes without a mapped value are `0`. This
987 // must be true of the "compute"/"constrain" function uses and member defs because circuits
988 // must be acyclic. This is likely true to for the symbol use graph is general but if a
989 // counterexample is ever found, the algorithm below must be re-evaluated.
990 assert(!hasCycle(&useGraph));
991
992 // Traverse "constrain" function nodes to compute their complexity and an inlining plan. Use
993 // post-order traversal so the complexity of all successor nodes is computed before computing
994 // the current node's complexity.
995 for (const SymbolUseGraphNode *currentNode : llvm::post_order(&useGraph)) {
996 LLVM_DEBUG(llvm::dbgs() << "\ncurrentNode = " << currentNode->toString());
997 if (!currentNode->isRealNode()) {
998 continue;
999 }
1000 if (currentNode->isStructParam()) {
1001 // Try to get the location of the StructDefOp to report an error.
1002 Operation *lookupFrom = currentNode->getSymbolPathRoot().getOperation();
1003 SymbolRefAttr prefix = getPrefixAsSymbolRefAttr(currentNode->getSymbolPath());
1004 auto res = lookupSymbolIn<StructDefOp>(tables, prefix, lookupFrom, lookupFrom, false);
1005 // If that lookup didn't work for some reason, report at the path root location.
1006 Operation *reportLoc = succeeded(res) ? res->get() : lookupFrom;
1007 return reportLoc->emitError("Cannot inline structs with parameters.");
1008 }
1009 FailureOr<FuncDefOp> currentFuncOpt = getIfStructConstrain(currentNode, tables);
1010 if (failed(currentFuncOpt)) {
1011 continue;
1012 }
1013 FuncDefOp currentFunc = currentFuncOpt.value();
1014 uint64_t currentComplexity = complexity(currentFunc);
1015 // If the current complexity is already too high, store it and continue.
1016 if (exceedsMaxComplexity(currentComplexity)) {
1017 complexityMemo[currentNode] = currentComplexity;
1018 continue;
1019 }
1020 // Otherwise, make a plan that adds successor "constrain" functions unless the
1021 // complexity becomes too high by adding that successor.
1022 SmallVector<StructDefOp> successorsToMerge;
1023 for (const SymbolUseGraphNode *successor : currentNode->successorIter()) {
1024 LLVM_DEBUG(llvm::dbgs().indent(2) << "successor: " << successor->toString() << '\n');
1025 // Note: all "constrain" function nodes will have a value, and all other nodes will not.
1026 auto memoResult = complexityMemo.find(successor);
1027 if (memoResult == complexityMemo.end()) {
1028 continue; // inner loop
1029 }
1030 uint64_t sComplexity = memoResult->second;
1031 assert(
1032 sComplexity <= (std::numeric_limits<uint64_t>::max() - currentComplexity) &&
1033 "addition will overflow"
1034 );
1035 uint64_t potentialComplexity = currentComplexity + sComplexity;
1036 if (!exceedsMaxComplexity(potentialComplexity)) {
1037 currentComplexity = potentialComplexity;
1038 FailureOr<FuncDefOp> successorFuncOpt = getIfStructConstrain(successor, tables);
1039 assert(succeeded(successorFuncOpt)); // follows from the Note above
1040 FuncDefOp successorFunc = successorFuncOpt.value();
1041 if (canInline(currentFunc, successorFunc)) {
1042 successorsToMerge.push_back(getParentStruct(successorFunc));
1043 }
1044 }
1045 }
1046 complexityMemo[currentNode] = currentComplexity;
1047 if (!successorsToMerge.empty()) {
1048 retVal.emplace_back(getParentStruct(currentFunc), std::move(successorsToMerge));
1049 }
1050 }
1051 LLVM_DEBUG({
1052 llvm::dbgs() << "-----------------------------------------------------------------\n";
1053 llvm::dbgs() << "InlineStructsPass plan:\n";
1054 for (auto &[caller, callees] : retVal) {
1055 llvm::dbgs().indent(2) << "inlining the following into \"" << caller.getSymName() << "\"\n";
1056 for (StructDefOp c : callees) {
1057 llvm::dbgs().indent(4) << "\"" << c.getSymName() << "\"\n";
1058 }
1059 }
1060 llvm::dbgs() << "-----------------------------------------------------------------\n";
1061 });
1062 return retVal;
1063 }
1064
1065public:
1066 void runOnOperation() override {
1067 const SymbolUseGraph &useGraph = getAnalysis<SymbolUseGraph>();
1068 LLVM_DEBUG(useGraph.dumpToDotFile());
1069
1070 SymbolTableCollection tables;
1071 FailureOr<InliningPlan> plan = makePlan(useGraph, tables);
1072 if (failed(plan)) {
1073 signalPassFailure(); // error already printed w/in makePlan()
1074 return;
1075 }
1076
1077 if (failed(performInlining(tables, plan.value()))) {
1078 signalPassFailure();
1079 return;
1080 };
1081 }
1082};
1083
1084} // namespace
1085
1086std::unique_ptr<mlir::Pass> llzk::createInlineStructsPass() {
1087 return std::make_unique<InlineStructsPass>();
1088};
Apache License January AND DISTRIBUTION Definitions License shall mean the terms and conditions for use
Definition LICENSE.txt:9
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 from
Definition LICENSE.txt:45
LogicalResult performInlining(SymbolTableCollection &tables, InliningPlan &plan)
mlir::SmallVector< std::pair< llzk::component::StructDefOp, mlir::SmallVector< llzk::component::StructDefOp > > > InliningPlan
Maps caller struct to callees that should be inlined.
#define check(x)
Definition Ops.cpp:171
This file defines methods symbol lookup across LLZK operations and included files.
static std::string from(mlir::Type type)
Return a brief string representation of the given LLZK type.
Definition TypeHelper.h:52
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
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
mlir::FailureOr< SymbolLookupResultUntyped > lookupSymbol(mlir::SymbolTableCollection &tables, bool reportMissing=true) const
void dumpToDotFile(std::string filename="") const
Dump the graph to file in dot graph format.
::mlir::TypedValue<::llzk::component::StructType > getComponent()
Definition Ops.h.inc:687
::llvm::StringRef getMemberName()
Definition Ops.cpp.inc:986
::mlir::FailureOr< SymbolLookupResult< MemberDefOp > > getMemberDefOp(::mlir::SymbolTableCollection &tables)
Gets the definition for the member referenced in this op.
Definition Ops.cpp:620
::mlir::TypedValue<::llzk::component::StructType > getComponent()
Gets the SSA value with the target component from the MemberRefOp.
void setMemberName(::llvm::StringRef attrValue)
Sets the member name attribute value in the MemberRefOp.
::llvm::StringRef getMemberName()
Gets the member name attribute value from the MemberRefOp.
::mlir::OpOperand & getComponentMutable()
Gets the SSA value with the target component from the MemberRefOp.
::llvm::StringRef getSymName()
Definition Ops.cpp.inc:1676
::llzk::function::FuncDefOp getConstrainFuncOp()
Gets the FuncDefOp that defines the constrain function in this structure, if present,...
Definition Ops.cpp:430
::llzk::function::FuncDefOp getComputeFuncOp()
Gets the FuncDefOp that defines the compute function in this structure, if present,...
Definition Ops.cpp:426
void print(::mlir::OpAsmPrinter &_odsPrinter)
Definition Ops.cpp.inc:1805
::mlir::Operation::operand_range getArgOperands()
Definition Ops.h.inc:241
::mlir::Value getSelfValueFromConstrain()
Return the "self" value (i.e.
Definition Ops.cpp:783
::mlir::OperandRangeRange getMapOperands()
Definition Ops.h.inc:245
static ::llvm::SmallVector<::mlir::ValueRange > toVectorOfValueRange(::mlir::OperandRangeRange)
Allocate consecutive storage of the ValueRange instances in the parameter so it can be passed to the ...
Definition Ops.cpp:813
::mlir::Value getSelfValueFromCompute()
Return the "self" value (i.e.
Definition Ops.cpp:778
::mlir::FailureOr<::llzk::SymbolLookupResult<::llzk::function::FuncDefOp > > getCalleeTarget(::mlir::SymbolTableCollection &tables)
Resolve and return the target FuncDefOp for this CallOp.
Definition Ops.cpp:788
::mlir::DenseI32ArrayAttr getNumDimsPerMapAttr()
Definition Ops.h.inc:272
FuncDefOp clone(::mlir::IRMapping &mapper)
Create a deep copy of this function and all of its blocks, remapping any operands that use values out...
::mlir::Value getSelfValueFromCompute()
Return the "self" value (i.e.
Definition Ops.cpp:354
::mlir::FunctionType getFunctionType()
Definition Ops.cpp.inc:952
::mlir::Value getSelfValueFromConstrain()
Return the "self" value (i.e.
Definition Ops.cpp:373
bool nameIsCompute()
Return true iff the function name is FUNC_NAME_COMPUTE (if needed, a check that this FuncDefOp is loc...
Definition Ops.h.inc:778
bool nameIsConstrain()
Return true iff the function name is FUNC_NAME_CONSTRAIN (if needed, a check that this FuncDefOp is l...
Definition Ops.h.inc:782
bool isStructConstrain()
Return true iff the function is within a StructDefOp and named FUNC_NAME_CONSTRAIN.
Definition Ops.h.inc:795
::mlir::SymbolRefAttr getFullyQualifiedName(bool requireParent=true)
Return the full name for this function from the root module, including all surrounding symbol table n...
Definition Ops.cpp:344
::mlir::Region & getBody()
Definition Ops.h.inc:607
std::string toStringOne(const T &value)
Definition Debug.h:175
std::string toStringList(InputIt begin, InputIt end)
Generate a comma-separated string representation by traversing elements from begin to end where the e...
Definition Debug.h:149
mlir::SymbolRefAttr getPrefixAsSymbolRefAttr(mlir::SymbolRefAttr symbol)
Return SymbolRefAttr like the one given but with the leaf/final element removed.
uint64_t computeEmitEqCardinality(Type type)
constexpr char FUNC_NAME_CONSTRAIN[]
Definition Constants.h:17
bool structTypesUnify(StructType lhs, StructType rhs, ArrayRef< StringRef > rhsReversePrefix, UnificationMap *unifications)
mlir::DenseMap< std::pair< mlir::SymbolRefAttr, Side >, mlir::Attribute > UnificationMap
Optional result from type unifications.
Definition TypeHelper.h:183
mlir::FailureOr< SymbolLookupResultUntyped > lookupSymbolIn(mlir::SymbolTableCollection &tables, mlir::SymbolRefAttr symbol, Within &&lookupWithin, mlir::Operation *origin, bool reportMissing=true)
mlir::FailureOr< OpClass > getParentOfType(mlir::Operation *op)
Return the closest surrounding parent operation that is of type 'OpClass'.
Definition OpHelpers.h:45
std::unique_ptr< mlir::Pass > createInlineStructsPass()
bool hasCycle(const GraphT &G)
Definition GraphUtil.h:17