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