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