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