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