35#include <mlir/IR/BuiltinOps.h>
36#include <mlir/Transforms/Inliner.h>
37#include <mlir/Transforms/InliningUtils.h>
38#include <mlir/Transforms/WalkPatternRewriteDriver.h>
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>
52#define GEN_PASS_DEF_INLINESTRUCTSPASS
62#define DEBUG_TYPE "llzk-inline-structs"
71using SrcStructMemberToCloneInDest = std::map<StringRef, DestCloneOfSrcStructMember>;
74using DestToSrcToClonedSrcInDest =
75 DenseMap<DestMemberWithSrcStructType, SrcStructMemberToCloneInDest>;
79static inline Value getSelfValue(
FuncDefOp f) {
85 llvm_unreachable(
"expected \"@compute\" or \"@constrain\" function");
99static FailureOr<MemberWriteOp>
100findOpThatStoresSubcmp(Value writtenValue, function_ref<InFlightDiagnostic()> emitError) {
102 for (Operation *user : writtenValue.getUsers()) {
103 if (
MemberWriteOp writeOp = llvm::dyn_cast<MemberWriteOp>(user)) {
105 if (writeOp.getVal() == writtenValue) {
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");
113 foundWrite = writeOp;
120 return emitError().append(
"result should be written to a member.");
128static bool combineHelper(
133 llvm::dbgs() <<
"[combineHelper] " << readOp <<
" => " << destMemberRefOp <<
'\n';
136 auto srcToClone = destToSrcToClone.find(getDef(tables, destMemberRefOp));
137 if (srcToClone == destToSrcToClone.end()) {
140 SrcStructMemberToCloneInDest oldToNewMembers = srcToClone->second;
142 if (resNewMember == oldToNewMembers.end()) {
147 OpBuilder builder(readOp);
149 builder, readOp.getLoc(), readOp.getType(), destMemberRefOp.
getComponent(),
150 resNewMember->second.getNameAttr()
152 readOp.replaceAllUsesWith(newRead.getOperation());
170static bool combineReadChain(
172 const DestToSrcToClonedSrcInDest &destToSrcToClone
174 LLVM_DEBUG({ llvm::dbgs() <<
"[combineReadChain] " << readOp <<
'\n'; });
177 llvm::dyn_cast_if_present<MemberReadOp>(readOp.
getComponent().getDefiningOp());
178 if (!readThatDefinesBaseComponent) {
181 return combineHelper(readOp, tables, destToSrcToClone, readThatDefinesBaseComponent);
200static LogicalResult combineNewThenReadChain(
202 const DestToSrcToClonedSrcInDest &destToSrcToClone
204 LLVM_DEBUG({ llvm::dbgs() <<
"[combineNewThenReadChain] " << readOp <<
'\n'; });
207 llvm::dyn_cast_if_present<CreateStructOp>(readOp.
getComponent().getDefiningOp());
208 if (!createThatDefinesBaseComponent) {
211 FailureOr<MemberWriteOp> foundWrite =
212 findOpThatStoresSubcmp(createThatDefinesBaseComponent, [&createThatDefinesBaseComponent]() {
213 return createThatDefinesBaseComponent.emitOpError();
215 if (failed(foundWrite)) {
218 return success(combineHelper(readOp, tables, destToSrcToClone, foundWrite.value()));
221static inline MemberReadOp getMemberReadThatDefinesSelfValuePassedToConstrain(
CallOp callOp) {
223 return llvm::dyn_cast_if_present<MemberReadOp>(selfArgFromCall.getDefiningOp());
228struct PendingErasure {
229 SmallPtrSet<Operation *, 8> memberReadOps;
230 SmallPtrSet<Operation *, 8> memberWriteOps;
231 SmallVector<CreateStructOp> newStructOps;
232 SmallVector<DestMemberWithSrcStructType> memberDefs;
237 SymbolTableCollection &tables;
238 PendingErasure &toDelete;
240 StructDefOp srcStruct;
242 StructDefOp destStruct;
244 inline MemberDefOp getDef(MemberRefOpInterface fRef)
const { return ::getDef(tables, fRef); }
252 class MemberRefRewriter final :
public OpInterfaceRewritePattern<MemberRefOpInterface> {
260 const SrcStructMemberToCloneInDest &oldToNewMembers;
264 FuncDefOp originalFunc, Value newRefBase,
265 const SrcStructMemberToCloneInDest &oldToNewMemberDef
267 : OpInterfaceRewritePattern(originalFunc.getContext()), funcRef(originalFunc),
268 oldBaseVal(nullptr), newBaseVal(newRefBase), oldToNewMembers(oldToNewMemberDef) {}
270 LogicalResult matchAndRewrite(MemberRefOpInterface op, PatternRewriter &rewriter)
const final {
275 if (op.getComponent() != oldBaseVal || !oldToNewMembers.contains(op.getMemberName())) {
278 rewriter.modifyOpInPlace(op, [
this, &op]() {
279 DestCloneOfSrcStructMember newF = oldToNewMembers.at(op.getMemberName());
280 op.setMemberName(newF.getSymName());
281 op.getComponentMutable().set(this->newBaseVal);
288 static FuncDefOp cloneWithMemberRefUpdate(std::unique_ptr<MemberRefRewriter> thisPat) {
290 FuncDefOp srcFuncClone = thisPat->funcRef.clone(mapper);
292 thisPat->funcRef = srcFuncClone;
293 thisPat->oldBaseVal = getSelfValue(srcFuncClone);
295 MLIRContext *ctx = thisPat->getContext();
296 RewritePatternSet patterns(ctx, std::move(thisPat));
297 walkAndApplyPatterns(srcFuncClone, std::move(patterns));
306 const StructInliner &data;
307 const DestToSrcToClonedSrcInDest &destToSrcToClone;
311 virtual MemberRefOpInterface getSelfRefMember(CallOp callOp) = 0;
312 virtual void processCloneBeforeInlining(FuncDefOp func) {}
313 virtual ~ImplBase() =
default;
316 ImplBase(
const StructInliner &inliner,
const DestToSrcToClonedSrcInDest &destToSrcToCloneRef)
317 : data(inliner), destToSrcToClone(destToSrcToCloneRef) {}
319 LogicalResult doInlining(FuncDefOp srcFunc, FuncDefOp destFunc) {
321 llvm::dbgs() <<
"[doInlining] SOURCE FUNCTION:\n";
323 llvm::dbgs() <<
"[doInlining] DESTINATION FUNCTION:\n";
327 InlinerInterface inliner(destFunc.getContext());
328 InlinerConfig inlinerConfig;
331 auto callHandler = [
this, &inliner, &inlinerConfig, &srcFunc](CallOp callOp) {
333 auto callOpTarget = callOp.getCalleeTarget(this->data.tables);
334 assert(succeeded(callOpTarget));
335 if (callOpTarget->get() != srcFunc) {
336 return WalkResult::advance();
341 MemberRefOpInterface selfMemberRefOp = this->getSelfRefMember(callOp);
342 if (!selfMemberRefOp) {
344 return WalkResult::interrupt();
350 FuncDefOp srcFuncClone = MemberRefRewriter::cloneWithMemberRefUpdate(
351 std::make_unique<MemberRefRewriter>(
353 this->destToSrcToClone.at(this->data.getDef(selfMemberRefOp))
356 this->processCloneBeforeInlining(srcFuncClone);
359 LogicalResult inlineCallRes = inlineCall(
360 inliner, inlinerConfig.getCloneCallback(), callOp, srcFuncClone,
363 if (failed(inlineCallRes)) {
365 return WalkResult::interrupt();
367 srcFuncClone.erase();
369 return WalkResult::skip();
372 auto memberWriteHandler = [
this](MemberWriteOp writeOp) {
374 if (this->destToSrcToClone.contains(this->data.getDef(writeOp))) {
375 this->data.toDelete.memberWriteOps.insert(writeOp);
377 return WalkResult::advance();
382 auto memberReadHandler = [
this](MemberReadOp readOp) {
384 if (combineReadChain(readOp, this->data.tables, destToSrcToClone)) {
385 return WalkResult::skip();
387 if (this->destToSrcToClone.contains(this->data.getDef(readOp))) {
388 this->data.toDelete.memberReadOps.insert(readOp);
390 return WalkResult::advance();
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(); });
401 return failure(walkRes.wasInterrupted());
405 class ConstrainImpl :
public ImplBase {
406 using ImplBase::ImplBase;
408 MemberRefOpInterface getSelfRefMember(CallOp callOp)
override {
409 LLVM_DEBUG({ llvm::dbgs() <<
"[ConstrainImpl::getSelfRefMember] " << callOp <<
'\n'; });
414 MemberRefOpInterface selfMemberRef =
415 getMemberReadThatDefinesSelfValuePassedToConstrain(callOp);
417 selfMemberRef.getComponent().getType() == this->data.destStruct.getType()) {
418 return selfMemberRef;
423 "\" to be passed a value read from a member in the current stuct."
430 class ComputeImpl :
public ImplBase {
431 using ImplBase::ImplBase;
433 MemberRefOpInterface getSelfRefMember(CallOp callOp)
override {
434 LLVM_DEBUG({ llvm::dbgs() <<
"[ComputeImpl::getSelfRefMember] " << callOp <<
'\n'; });
441 FailureOr<MemberWriteOp> foundWrite =
443 return callOp.emitOpError().append(
"\"@", FUNC_NAME_COMPUTE,
"\" ");
445 return static_cast<MemberRefOpInterface
>(foundWrite.value_or(
nullptr));
448 void processCloneBeforeInlining(FuncDefOp func)
override {
452 func.
getBody().walk([
this](CreateStructOp newStructOp) {
453 if (newStructOp.getType() == this->data.srcStruct.getType()) {
454 this->data.toDelete.newStructOps.push_back(newStructOp);
463 DestToSrcToClonedSrcInDest cloneMembers() {
464 DestToSrcToClonedSrcInDest destToSrcToClone;
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())) {
474 assert(unifications.empty());
476 toDelete.memberDefs.push_back(destMember);
479 SrcStructMemberToCloneInDest &srcToClone = destToSrcToClone[destMember];
480 std::vector<MemberDefOp> srcMembers = srcStruct.getMemberDefs();
481 if (srcMembers.empty()) {
484 OpBuilder builder(destMember);
485 std::string newNameBase =
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;
492 destStructSymTable.insert(newF);
496 return destToSrcToClone;
500 inline LogicalResult inlineConstrainCall(
const DestToSrcToClonedSrcInDest &destToSrcToClone) {
501 return ConstrainImpl(*
this, destToSrcToClone)
502 .doInlining(srcStruct.getConstrainFuncOp(), destStruct.getConstrainFuncOp());
506 inline LogicalResult inlineComputeCall(
const DestToSrcToClonedSrcInDest &destToSrcToClone) {
507 return ComputeImpl(*
this, destToSrcToClone)
508 .doInlining(srcStruct.getComputeFuncOp(), destStruct.getComputeFuncOp());
513 SymbolTableCollection &tbls, PendingErasure &opsToDelete, StructDefOp
from, StructDefOp into
515 : tables(tbls), toDelete(opsToDelete), srcStruct(
from), destStruct(into) {}
517 FailureOr<DestToSrcToClonedSrcInDest> doInline() {
519 llvm::dbgs() <<
"[StructInliner] merge " << srcStruct.getSymNameAttr() <<
" into "
520 << destStruct.getSymNameAttr() <<
'\n'
523 DestToSrcToClonedSrcInDest destToSrcToClone = cloneMembers();
524 if (failed(inlineConstrainCall(destToSrcToClone)) ||
525 failed(inlineComputeCall(destToSrcToClone))) {
528 return destToSrcToClone;
534 { t.contains(p) } -> std::convertible_to<bool>;
538template <
typename... PendingDeletionSets>
540class DanglingUseHandler {
541 SymbolTableCollection &tables;
542 const DestToSrcToClonedSrcInDest &destToSrcToClone;
543 std::tuple<
const PendingDeletionSets &...> otherRefsToBeDeleted;
547 SymbolTableCollection &symTables,
const DestToSrcToClonedSrcInDest &destToSrcToCloneRef,
548 const PendingDeletionSets &...otherRefsPendingDeletion
550 : tables(symTables), destToSrcToClone(destToSrcToCloneRef),
551 otherRefsToBeDeleted(otherRefsPendingDeletion...) {}
558 LogicalResult handle(Operation *op)
const {
559 if (op->use_empty()) {
564 llvm::dbgs() <<
"[DanglingUseHandler::handle] op: " << *op <<
'\n';
565 llvm::dbgs() <<
"[DanglingUseHandler::handle] in function: "
566 << op->getParentOfType<
FuncDefOp>() <<
'\n';
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))) {
574 Operation *user =
use.getOwner();
576 if (!opWillBeDeleted(user)) {
577 return op->emitOpError()
579 "with use in '", user->getName().getStringRef(),
580 "' is not (currently) supported by this pass."
582 .attachNote(user->getLoc())
583 .append(
"used by this operation");
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");
605 inline LogicalResult handleUseInCallOp(OpOperand &
use,
CallOp inCall, Operation *origin)
const {
607 llvm::dbgs() <<
"[DanglingUseHandler::handleUseInCallOp] use in call: " << inCall <<
'\n'
609 unsigned argIdx =
use.getOperandNumber() - inCall.
getArgOperands().getBeginOperandIndex();
611 llvm::dbgs() <<
"[DanglingUseHandler::handleUseInCallOp] at index: " << argIdx <<
'\n'
615 if (failed(tgtFuncRes)) {
617 ->emitOpError(
"as argument to an unknown function is not supported by this pass.")
618 .attachNote(inCall.getLoc())
619 .append(
"used by this call");
623 llvm::dbgs() <<
"[DanglingUseHandler::handleUseInCallOp] call target: " << tgtFunc <<
'\n'
625 if (tgtFunc.isExternal()) {
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");
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");
646 llvm::dbgs() <<
"[DanglingUseHandler::handleUseInCallOp] member ref op for param: "
649 if (!paramFromMember) {
652 const SrcStructMemberToCloneInDest &newMembers =
653 destToSrcToClone.at(getDef(tables, paramFromMember));
655 llvm::dbgs() <<
"[DanglingUseHandler::handleUseInCallOp] members to split: "
660 splitFunctionParam(tgtFunc, argIdx, newMembers);
662 llvm::dbgs() <<
"[DanglingUseHandler::handleUseInCallOp] UPDATED call target: " << tgtFunc
664 llvm::dbgs() <<
"[DanglingUseHandler::handleUseInCallOp] UPDATED call target type: "
670 OpBuilder builder(inCall);
671 SmallVector<Value> splitArgs;
675 for (
auto [origName, newMemberRef] : newMembers) {
678 builder, inCall.getLoc(), newMemberRef.getType(), originalBaseVal,
679 newMemberRef.getNameAttr()
686 newOpArgs.erase(newOpArgs.begin() + argIdx), splitArgs.begin(), splitArgs.end()
689 inCall.replaceAllUsesWith(
691 builder, inCall.getLoc(), tgtFunc,
698 llvm::dbgs() <<
"[DanglingUseHandler::handleUseInCallOp] UPDATED function: "
699 << origin->getParentOfType<
FuncDefOp>() <<
'\n';
705 inline bool opWillBeDeleted(Operation *otherOp)
const {
706 return std::apply([&](
const auto &...sets) {
707 return ((sets.contains(otherOp)) || ...);
708 }, otherRefsToBeDeleted);
715 static void splitFunctionParam(
716 FuncDefOp func,
unsigned paramIdx,
const SrcStructMemberToCloneInDest &nameToNewMember
720 const SrcStructMemberToCloneInDest &newMembers;
721 std::optional<std::string> originalArgName;
722 SmallVector<std::string> existingArgNames;
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());
731 originalArgName = argName->getValue().str();
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());
747 SmallVector<Type> convertResults(ArrayRef<Type> origTypes)
override {
748 return SmallVector<Type>(origTypes);
750 ArrayAttr convertInputAttrs(ArrayAttr origAttrs, SmallVector<Type>)
override {
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);
761 for (
auto [memberName, _] : newMembers) {
762 std::string desiredName = (*originalArgName +
'.' + memberName).str();
768 splitAttrs.append(newMembers.size(), splitAttr);
770 newAttrs[inputIdx] = splitAttrs.front();
772 newAttrs.begin() + inputIdx + 1, splitAttrs.begin() + 1, splitAttrs.end()
774 return ArrayAttr::get(origAttrs.getContext(), newAttrs);
778 ArrayAttr convertResultAttrs(ArrayAttr origAttrs, SmallVector<Type>)
override {
782 void processBlockArgs(Block &entryBlock, RewriterBase &rewriter)
override {
783 Value oldStructRef = entryBlock.getArgument(inputIdx);
787 llvm::StringMap<BlockArgument> memberNameToNewArg;
788 Location loc = oldStructRef.getLoc();
789 unsigned idx = inputIdx;
790 for (
auto [memberName, newMember] : newMembers) {
792 BlockArgument newArg = entryBlock.insertArgument(++idx, newMember.getType(), loc);
793 memberNameToNewArg[memberName] = newArg;
798 for (OpOperand &oldBlockArgUse : llvm::make_early_inc_range(oldStructRef.getUses())) {
799 if (
MemberReadOp readOp = llvm::dyn_cast<MemberReadOp>(oldBlockArgUse.getOwner())) {
801 BlockArgument newArg = memberNameToNewArg.at(readOp.
getMemberName());
802 rewriter.replaceAllUsesWith(readOp, newArg);
803 rewriter.eraseOp(readOp);
808 llvm::errs() <<
"Unexpected use of " << oldBlockArgUse.get() <<
" in "
809 << *oldBlockArgUse.getOwner() <<
'\n';
810 llvm_unreachable(
"Not yet implemented");
814 entryBlock.eraseArgument(inputIdx);
817 IRRewriter rewriter(func.getContext());
818 Impl(func, paramIdx, nameToNewMember).convert(func, rewriter);
825static LogicalResult finalizeStruct(
826 SymbolTableCollection &tables,
StructDefOp caller, PendingErasure &&toDelete,
827 DestToSrcToClonedSrcInDest &&destToSrcToClone
830 llvm::dbgs() <<
"[finalizeStruct] dumping 'caller' struct before compressing chains:\n";
831 caller.
print(llvm::dbgs(), OpPrintingFlags().assumeVerified());
832 llvm::dbgs() <<
'\n';
837 combineReadChain(readOp, tables, destToSrcToClone);
841 auto res = computeFn.walk([&tables, &destToSrcToClone, &computeSelfVal](
MemberReadOp readOp) {
842 combineReadChain(readOp, tables, destToSrcToClone);
846 return WalkResult::advance();
848 return WalkResult(combineNewThenReadChain(readOp, tables, destToSrcToClone));
850 if (res.wasInterrupted()) {
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';
862 for (Operation *op : toDelete.memberWriteOps) {
863 llvm::dbgs().indent(2) << *op <<
'\n';
866 llvm::dbgs().indent(2) << op <<
'\n';
868 for (DestMemberWithSrcStructType op : toDelete.memberDefs) {
869 llvm::dbgs().indent(2) << op <<
'\n';
875 DanglingUseHandler<SmallPtrSet<Operation *, 8>, SmallPtrSet<Operation *, 8>> useHandler(
876 tables, destToSrcToClone, toDelete.memberWriteOps, toDelete.memberReadOps
879 if (failed(useHandler.handle(op))) {
885 for (Operation *op : toDelete.memberWriteOps) {
886 if (failed(useHandler.handle(op))) {
891 for (Operation *op : toDelete.memberReadOps) {
892 if (failed(useHandler.handle(op))) {
901 SymbolTable &callerSymTab = tables.getSymbolTable(caller);
902 for (DestMemberWithSrcStructType op : toDelete.memberDefs) {
903 assert(op.getParentOp() == caller);
904 callerSymTab.erase(op);
915 for (
auto &[caller, callees] : plan) {
918 PendingErasure toDelete;
920 DestToSrcToClonedSrcInDest aggregateReplacements;
923 FailureOr<DestToSrcToClonedSrcInDest> res =
924 StructInliner(tables, toDelete, toInline, caller).doInline();
929 for (
auto &[k, v] : res.value()) {
930 assert(!aggregateReplacements.contains(k) &&
"duplicate not possible");
931 aggregateReplacements[k] = std::move(v);
935 LogicalResult finalizeResult =
936 finalizeStruct(tables, caller, std::move(toDelete), std::move(aggregateReplacements));
937 if (failed(finalizeResult)) {
947 using Base = InlineStructsPassBase<PassImpl>;
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)) {
955 }
else if (
auto ee = llvm::dyn_cast<constrain::EmitEqualityOp>(op)) {
957 }
else if (
auto ec = llvm::dyn_cast<constrain::EmitContainmentOp>(op)) {
972 getIfResolvableStructConstrain(
const SymbolUseGraphNode *node, SymbolTableCollection &tables) {
977 if (failed(lookupRes)) {
980 FuncDefOp func = llvm::dyn_cast<FuncDefOp>(lookupRes->get());
989 static inline StructDefOp getParentStruct(FuncDefOp func) {
992 assert(currentNodeParentStruct);
993 return currentNodeParentStruct;
997 inline bool exceedsMaxComplexity(uint64_t
check) {
998 return maxComplexity > 0 &&
check > maxComplexity;
1004 canInline(FuncDefOp currentFunc, FuncDefOp successorFunc, SymbolTableCollection &tables) {
1016 WalkResult res = currentFunc.walk([successorFunc, &tables](CallOp c) {
1018 if (failed(calleeTarget) || calleeTarget->get() != successorFunc) {
1019 return WalkResult::advance();
1021 return getMemberReadThatDefinesSelfValuePassedToConstrain(c)
1022 ? WalkResult::interrupt()
1023 : WalkResult::advance();
1029 return res.wasInterrupted();
1034 static LogicalResult
1035 verifyNoTemplateSymbolBindings(
const SymbolUseGraph &useGraph, SymbolTableCollection &tables) {
1036 for (
const SymbolUseGraphNode *node : useGraph.
nodesIter()) {
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.";
1055 static LogicalResult emitConstrainReachableCycleError(
1056 ArrayRef<const SymbolUseGraphNode *> dfsStack,
const SymbolUseGraphNode *cycleHead,
1057 SymbolTableCollection &tables
1059 SmallVector<const SymbolUseGraphNode *, 8> cycle;
1060 bool inCycle =
false;
1061 for (
const SymbolUseGraphNode *node : dfsStack) {
1062 if (node == cycleHead) {
1066 cycle.push_back(node);
1069 if (cycle.empty()) {
1070 cycle.push_back(cycleHead);
1074 for (
const SymbolUseGraphNode *node : cycle) {
1079 if (failed(lookupRes)) {
1082 Operation *op = lookupRes->get();
1084 if (llvm::isa<FuncDefOp>(op)) {
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.";
1094 for (
const SymbolUseGraphNode *node : cycle) {
1098 if (
auto lookupRes = node->
lookupSymbol(tables,
false);
1099 succeeded(lookupRes)) {
1100 diag.attachNote(lookupRes->get()->getLoc()) <<
"cycle contains " << node->
getSymbolPath();
1115 static LogicalResult computeConstrainReachablePostOrder(
1116 const SymbolUseGraph &useGraph, SymbolTableCollection &tables,
1117 SmallVectorImpl<const SymbolUseGraphNode *> &postOrder
1119 enum class VisitState : std::uint8_t { Active, Done };
1121 DenseMap<const SymbolUseGraphNode *, VisitState> state;
1122 SmallVector<const SymbolUseGraphNode *, 32> dfsStack;
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);
1133 state[node] = VisitState::Active;
1134 dfsStack.push_back(node);
1135 for (
const SymbolUseGraphNode *successor : node->
successorIter()) {
1136 if (failed(self(self, successor))) {
1140 dfsStack.pop_back();
1142 state[node] = VisitState::Done;
1143 postOrder.push_back(node);
1147 for (
const SymbolUseGraphNode *node : useGraph.
nodesIter()) {
1148 if (!getIfResolvableStructConstrain(node, tables)) {
1151 if (failed(dfs(dfs, node))) {
1163 inline FailureOr<InliningPlan>
1164 makePlan(
const SymbolUseGraph &useGraph, SymbolTableCollection &tables) {
1166 llvm::dbgs() <<
"Running InlineStructsPass with max complexity ";
1167 if (maxComplexity == 0) {
1168 llvm::dbgs() <<
"unlimited";
1170 llvm::dbgs() << maxComplexity;
1172 llvm::dbgs() <<
'\n';
1175 DenseMap<const SymbolUseGraphNode *, uint64_t> complexityMemo;
1177 if (failed(verifyNoTemplateSymbolBindings(useGraph, tables))) {
1181 SmallVector<const SymbolUseGraphNode *, 32> constrainPostOrder;
1182 if (failed(computeConstrainReachablePostOrder(useGraph, tables, constrainPostOrder))) {
1189 for (
const SymbolUseGraphNode *currentNode : constrainPostOrder) {
1190 LLVM_DEBUG(llvm::dbgs() <<
"\ncurrentNode = " << currentNode->toString());
1191 FuncDefOp currentFunc = getIfResolvableStructConstrain(currentNode, tables);
1195 uint64_t currentComplexity = complexity(currentFunc);
1197 if (exceedsMaxComplexity(currentComplexity)) {
1198 complexityMemo[currentNode] = currentComplexity;
1203 SmallVector<StructDefOp> successorsToMerge;
1204 for (
const SymbolUseGraphNode *successor : currentNode->successorIter()) {
1205 LLVM_DEBUG(llvm::dbgs().indent(2) <<
"successor: " << successor->toString() <<
'\n');
1207 auto memoResult = complexityMemo.find(successor);
1208 if (memoResult == complexityMemo.end()) {
1211 uint64_t sComplexity = memoResult->second;
1213 sComplexity <= (std::numeric_limits<uint64_t>::max() - currentComplexity) &&
1214 "addition will overflow"
1216 uint64_t potentialComplexity = currentComplexity + sComplexity;
1217 if (!exceedsMaxComplexity(potentialComplexity)) {
1218 currentComplexity = potentialComplexity;
1219 FuncDefOp successorFunc = getIfResolvableStructConstrain(successor, tables);
1220 if (!successorFunc) {
1223 if (canInline(currentFunc, successorFunc, tables)) {
1224 successorsToMerge.push_back(getParentStruct(successorFunc));
1228 complexityMemo[currentNode] = currentComplexity;
1229 if (!successorsToMerge.empty()) {
1230 retVal.emplace_back(getParentStruct(currentFunc), std::move(successorsToMerge));
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";
1242 llvm::dbgs() <<
"-----------------------------------------------------------------\n";
1248 void runOnOperation()
override {
1249 const SymbolUseGraph &useGraph = getAnalysis<SymbolUseGraph>();
1252 SymbolTableCollection tables;
1253 FailureOr<InliningPlan> plan = makePlan(useGraph, tables);
1255 signalPassFailure();
1260 signalPassFailure();
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
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
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.
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()
static MemberReadOp create(::mlir::OpBuilder &builder, ::mlir::Location location, ::mlir::Type resultType, ::mlir::Value component, ::mlir::StringAttr member)
::llvm::StringRef getMemberName()
::mlir::FailureOr< SymbolLookupResult< MemberDefOp > > getMemberDefOp(::mlir::SymbolTableCollection &tables)
Gets the definition for the member referenced in this op.
::mlir::TypedValue<::llzk::component::StructType > getComponent()
Gets the SSA value with the target component from the MemberRefOp.
::llvm::StringRef getSymName()
::llzk::function::FuncDefOp getConstrainFuncOp()
Gets the FuncDefOp that defines the constrain function in this structure, if present,...
::llzk::function::FuncDefOp getComputeFuncOp()
Gets the FuncDefOp that defines the compute function in this structure, if present,...
void print(::mlir::OpAsmPrinter &_odsPrinter)
::mlir::Operation::operand_range getArgOperands()
static CallOp create(::mlir::OpBuilder &builder, ::mlir::Location location, ::mlir::TypeRange resultTypes, ::mlir::SymbolRefAttr callee, ::mlir::ValueRange argOperands={}, ::llvm::ArrayRef<::mlir::Attribute > templateParams={})
::mlir::Value getSelfValueFromConstrain()
Return the "self" value (i.e.
::mlir::OperandRangeRange getMapOperands()
::mlir::Value getSelfValueFromCompute()
Return the "self" value (i.e.
::mlir::FailureOr<::llzk::SymbolLookupResult<::llzk::function::FuncDefOp > > getCalleeTarget(::mlir::SymbolTableCollection &tables)
Resolve and return the target FuncDefOp for this CallOp.
::mlir::DenseI32ArrayAttr getNumDimsPerMapAttr()
::mlir::Value getSelfValueFromCompute()
Return the "self" value (i.e.
::mlir::FunctionType getFunctionType()
::mlir::Value getSelfValueFromConstrain()
Return the "self" value (i.e.
bool nameIsCompute()
Return true iff the function name is FUNC_NAME_COMPUTE (if needed, a check that this FuncDefOp is loc...
bool nameIsConstrain()
Return true iff the function name is FUNC_NAME_CONSTRAIN (if needed, a check that this FuncDefOp is l...
bool isStructConstrain()
Return true iff the function is within a StructDefOp and named FUNC_NAME_CONSTRAIN.
::mlir::SymbolRefAttr getFullyQualifiedName(bool requireParent=true)
Return the full name for this function from the root module, including all surrounding symbol table n...
::mlir::Region & getBody()
std::string toStringOne(const T &value)
std::string toStringList(InputIt begin, InputIt end)
Generate a comma-separated string representation by traversing elements from begin to end where the e...
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[]
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.
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'.
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.