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>
51#define GEN_PASS_DEF_INLINESTRUCTSPASS
61#define DEBUG_TYPE "llzk-inline-structs"
70using SrcStructMemberToCloneInDest = std::map<StringRef, DestCloneOfSrcStructMember>;
73using DestToSrcToClonedSrcInDest =
74 DenseMap<DestMemberWithSrcStructType, SrcStructMemberToCloneInDest>;
78static inline Value getSelfValue(
FuncDefOp f) {
84 llvm_unreachable(
"expected \"@compute\" or \"@constrain\" function");
98static FailureOr<MemberWriteOp>
99findOpThatStoresSubcmp(Value writtenValue, function_ref<InFlightDiagnostic()> emitError) {
101 for (Operation *user : writtenValue.getUsers()) {
102 if (
MemberWriteOp writeOp = llvm::dyn_cast<MemberWriteOp>(user)) {
104 if (writeOp.getVal() == writtenValue) {
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");
112 foundWrite = writeOp;
119 return emitError().append(
"result should be written to a member.");
127static bool combineHelper(
132 llvm::dbgs() <<
"[combineHelper] " << readOp <<
" => " << destMemberRefOp <<
'\n';
135 auto srcToClone = destToSrcToClone.find(getDef(tables, destMemberRefOp));
136 if (srcToClone == destToSrcToClone.end()) {
139 SrcStructMemberToCloneInDest oldToNewMembers = srcToClone->second;
140 auto resNewMember = oldToNewMembers.find(readOp.
getMemberName());
141 if (resNewMember == oldToNewMembers.end()) {
146 OpBuilder builder(readOp);
148 readOp.getLoc(), readOp.getType(), destMemberRefOp.
getComponent(),
149 resNewMember->second.getNameAttr()
151 readOp.replaceAllUsesWith(newRead.getOperation());
169static bool combineReadChain(
171 const DestToSrcToClonedSrcInDest &destToSrcToClone
173 LLVM_DEBUG({ llvm::dbgs() <<
"[combineReadChain] " << readOp <<
'\n'; });
176 llvm::dyn_cast_if_present<MemberReadOp>(readOp.
getComponent().getDefiningOp());
177 if (!readThatDefinesBaseComponent) {
180 return combineHelper(readOp, tables, destToSrcToClone, readThatDefinesBaseComponent);
199static LogicalResult combineNewThenReadChain(
201 const DestToSrcToClonedSrcInDest &destToSrcToClone
203 LLVM_DEBUG({ llvm::dbgs() <<
"[combineNewThenReadChain] " << readOp <<
'\n'; });
206 llvm::dyn_cast_if_present<CreateStructOp>(readOp.
getComponent().getDefiningOp());
207 if (!createThatDefinesBaseComponent) {
210 FailureOr<MemberWriteOp> foundWrite =
211 findOpThatStoresSubcmp(createThatDefinesBaseComponent, [&createThatDefinesBaseComponent]() {
212 return createThatDefinesBaseComponent.emitOpError();
214 if (failed(foundWrite)) {
217 return success(combineHelper(readOp, tables, destToSrcToClone, foundWrite.value()));
220static inline MemberReadOp getMemberReadThatDefinesSelfValuePassedToConstrain(
CallOp callOp) {
222 return llvm::dyn_cast_if_present<MemberReadOp>(selfArgFromCall.getDefiningOp());
227struct PendingErasure {
228 SmallPtrSet<Operation *, 8> memberReadOps;
229 SmallPtrSet<Operation *, 8> memberWriteOps;
230 SmallVector<CreateStructOp> newStructOps;
231 SmallVector<DestMemberWithSrcStructType> memberDefs;
236 SymbolTableCollection &tables;
237 PendingErasure &toDelete;
239 StructDefOp srcStruct;
241 StructDefOp destStruct;
243 inline MemberDefOp getDef(MemberRefOpInterface fRef)
const { return ::getDef(tables, fRef); }
251 class MemberRefRewriter final :
public OpInterfaceRewritePattern<MemberRefOpInterface> {
259 const SrcStructMemberToCloneInDest &oldToNewMembers;
263 FuncDefOp originalFunc, Value newRefBase,
264 const SrcStructMemberToCloneInDest &oldToNewMemberDef
266 : OpInterfaceRewritePattern(originalFunc.getContext()), funcRef(originalFunc),
267 oldBaseVal(nullptr), newBaseVal(newRefBase), oldToNewMembers(oldToNewMemberDef) {}
269 LogicalResult match(MemberRefOpInterface op)
const final {
275 op.getComponent() == oldBaseVal && oldToNewMembers.contains(op.getMemberName())
279 void rewrite(MemberRefOpInterface op, PatternRewriter &rewriter)
const final {
280 rewriter.modifyOpInPlace(op, [
this, &op]() {
281 DestCloneOfSrcStructMember newF = oldToNewMembers.at(op.getMemberName());
282 op.setMemberName(newF.getSymName());
283 op.getComponentMutable().set(this->newBaseVal);
289 static FuncDefOp cloneWithMemberRefUpdate(std::unique_ptr<MemberRefRewriter> thisPat) {
291 FuncDefOp srcFuncClone = thisPat->funcRef.clone(mapper);
293 thisPat->funcRef = srcFuncClone;
294 thisPat->oldBaseVal = getSelfValue(srcFuncClone);
296 MLIRContext *ctx = thisPat->getContext();
297 RewritePatternSet patterns(ctx, std::move(thisPat));
298 walkAndApplyPatterns(srcFuncClone, std::move(patterns));
307 const StructInliner &data;
308 const DestToSrcToClonedSrcInDest &destToSrcToClone;
312 virtual MemberRefOpInterface getSelfRefMember(CallOp callOp) = 0;
313 virtual void processCloneBeforeInlining(FuncDefOp func) {}
314 virtual ~ImplBase() =
default;
317 ImplBase(
const StructInliner &inliner,
const DestToSrcToClonedSrcInDest &destToSrcToCloneRef)
318 : data(inliner), destToSrcToClone(destToSrcToCloneRef) {}
320 LogicalResult doInlining(FuncDefOp srcFunc, FuncDefOp destFunc) {
322 llvm::dbgs() <<
"[doInlining] SOURCE FUNCTION:\n";
324 llvm::dbgs() <<
"[doInlining] DESTINATION FUNCTION:\n";
328 InlinerInterface inliner(destFunc.getContext());
331 auto callHandler = [
this, &inliner, &srcFunc](CallOp callOp) {
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 =
360 inlineCall(inliner, callOp, srcFuncClone, &srcFuncClone.
getBody(),
false);
361 if (failed(inlineCallRes)) {
363 return WalkResult::interrupt();
365 srcFuncClone.erase();
367 return WalkResult::skip();
370 auto memberWriteHandler = [
this](MemberWriteOp writeOp) {
372 if (this->destToSrcToClone.contains(this->data.getDef(writeOp))) {
373 this->data.toDelete.memberWriteOps.insert(writeOp);
375 return WalkResult::advance();
380 auto memberReadHandler = [
this](MemberReadOp readOp) {
382 if (combineReadChain(readOp, this->data.tables, destToSrcToClone)) {
383 return WalkResult::skip();
385 if (this->destToSrcToClone.contains(this->data.getDef(readOp))) {
386 this->data.toDelete.memberReadOps.insert(readOp);
388 return WalkResult::advance();
391 WalkResult walkRes = destFunc.
getBody().walk<WalkOrder::PreOrder>([&](Operation *op) {
392 return TypeSwitch<Operation *, WalkResult>(op)
393 .Case<CallOp>(callHandler)
394 .Case<MemberWriteOp>(memberWriteHandler)
395 .Case<MemberReadOp>(memberReadHandler)
396 .Default([](Operation *) {
return WalkResult::advance(); });
399 return failure(walkRes.wasInterrupted());
403 class ConstrainImpl :
public ImplBase {
404 using ImplBase::ImplBase;
406 MemberRefOpInterface getSelfRefMember(CallOp callOp)
override {
407 LLVM_DEBUG({ llvm::dbgs() <<
"[ConstrainImpl::getSelfRefMember] " << callOp <<
'\n'; });
412 MemberRefOpInterface selfMemberRef =
413 getMemberReadThatDefinesSelfValuePassedToConstrain(callOp);
415 selfMemberRef.getComponent().getType() == this->data.destStruct.getType()) {
416 return selfMemberRef;
421 "\" to be passed a value read from a member in the current stuct."
428 class ComputeImpl :
public ImplBase {
429 using ImplBase::ImplBase;
431 MemberRefOpInterface getSelfRefMember(CallOp callOp)
override {
432 LLVM_DEBUG({ llvm::dbgs() <<
"[ComputeImpl::getSelfRefMember] " << callOp <<
'\n'; });
439 FailureOr<MemberWriteOp> foundWrite =
441 return callOp.emitOpError().append(
"\"@", FUNC_NAME_COMPUTE,
"\" ");
443 return static_cast<MemberRefOpInterface
>(foundWrite.value_or(
nullptr));
446 void processCloneBeforeInlining(FuncDefOp func)
override {
450 func.
getBody().walk([
this](CreateStructOp newStructOp) {
451 if (newStructOp.getType() == this->data.srcStruct.getType()) {
452 this->data.toDelete.newStructOps.push_back(newStructOp);
461 DestToSrcToClonedSrcInDest cloneMembers() {
462 DestToSrcToClonedSrcInDest destToSrcToClone;
464 SymbolTable &destStructSymTable = tables.getSymbolTable(destStruct);
465 StructType srcStructType = srcStruct.getType();
466 for (MemberDefOp destMember : destStruct.getMemberDefs()) {
467 if (StructType destMemberType = llvm::dyn_cast<StructType>(destMember.getType())) {
472 assert(unifications.empty());
474 toDelete.memberDefs.push_back(destMember);
477 SrcStructMemberToCloneInDest &srcToClone = destToSrcToClone[destMember];
478 std::vector<MemberDefOp> srcMembers = srcStruct.getMemberDefs();
479 if (srcMembers.empty()) {
482 OpBuilder builder(destMember);
483 std::string newNameBase =
485 for (MemberDefOp srcMember : srcMembers) {
486 DestCloneOfSrcStructMember newF = llvm::cast<MemberDefOp>(builder.clone(*srcMember));
487 newF.setName(builder.getStringAttr(newNameBase +
'+' + newF.getName()));
488 srcToClone[srcMember.getSymNameAttr()] = newF;
490 destStructSymTable.insert(newF);
494 return destToSrcToClone;
498 inline LogicalResult inlineConstrainCall(
const DestToSrcToClonedSrcInDest &destToSrcToClone) {
499 return ConstrainImpl(*
this, destToSrcToClone)
500 .doInlining(srcStruct.getConstrainFuncOp(), destStruct.getConstrainFuncOp());
504 inline LogicalResult inlineComputeCall(
const DestToSrcToClonedSrcInDest &destToSrcToClone) {
505 return ComputeImpl(*
this, destToSrcToClone)
506 .doInlining(srcStruct.getComputeFuncOp(), destStruct.getComputeFuncOp());
511 SymbolTableCollection &tbls, PendingErasure &opsToDelete, StructDefOp
from, StructDefOp into
513 : tables(tbls), toDelete(opsToDelete), srcStruct(
from), destStruct(into) {}
515 FailureOr<DestToSrcToClonedSrcInDest> doInline() {
517 llvm::dbgs() <<
"[StructInliner] merge " << srcStruct.getSymNameAttr() <<
" into "
518 << destStruct.getSymNameAttr() <<
'\n'
521 DestToSrcToClonedSrcInDest destToSrcToClone = cloneMembers();
522 if (failed(inlineConstrainCall(destToSrcToClone)) ||
523 failed(inlineComputeCall(destToSrcToClone))) {
526 return destToSrcToClone;
532 { t.contains(p) } -> std::convertible_to<bool>;
536template <
typename... PendingDeletionSets>
538class DanglingUseHandler {
539 SymbolTableCollection &tables;
540 const DestToSrcToClonedSrcInDest &destToSrcToClone;
541 std::tuple<
const PendingDeletionSets &...> otherRefsToBeDeleted;
545 SymbolTableCollection &symTables,
const DestToSrcToClonedSrcInDest &destToSrcToCloneRef,
546 const PendingDeletionSets &...otherRefsPendingDeletion
548 : tables(symTables), destToSrcToClone(destToSrcToCloneRef),
549 otherRefsToBeDeleted(otherRefsPendingDeletion...) {}
556 LogicalResult handle(Operation *op)
const {
557 if (op->use_empty()) {
562 llvm::dbgs() <<
"[DanglingUseHandler::handle] op: " << *op <<
'\n';
563 llvm::dbgs() <<
"[DanglingUseHandler::handle] in function: "
564 << op->getParentOfType<
FuncDefOp>() <<
'\n';
566 for (OpOperand &
use : llvm::make_early_inc_range(op->getUses())) {
567 if (
CallOp c = llvm::dyn_cast<CallOp>(
use.getOwner())) {
568 if (failed(handleUseInCallOp(
use, c, op))) {
572 Operation *user =
use.getOwner();
574 if (!opWillBeDeleted(user)) {
575 return op->emitOpError()
577 "with use in '", user->getName().getStringRef(),
578 "' is not (currently) supported by this pass."
580 .attachNote(user->getLoc())
581 .append(
"used by this operation");
586 if (!op->use_empty()) {
587 for (Operation *user : op->getUsers()) {
588 if (!opWillBeDeleted(user)) {
589 llvm::errs() <<
"Op has remaining use(s) that could not be removed: " << *op <<
'\n';
590 llvm_unreachable(
"Expected all uses to be removed");
603 inline LogicalResult handleUseInCallOp(OpOperand &
use,
CallOp inCall, Operation *origin)
const {
605 llvm::dbgs() <<
"[DanglingUseHandler::handleUseInCallOp] use in call: " << inCall <<
'\n'
607 unsigned argIdx =
use.getOperandNumber() - inCall.
getArgOperands().getBeginOperandIndex();
609 llvm::dbgs() <<
"[DanglingUseHandler::handleUseInCallOp] at index: " << argIdx <<
'\n'
613 if (failed(tgtFuncRes)) {
615 ->emitOpError(
"as argument to an unknown function is not supported by this pass.")
616 .attachNote(inCall.getLoc())
617 .append(
"used by this call");
621 llvm::dbgs() <<
"[DanglingUseHandler::handleUseInCallOp] call target: " << tgtFunc <<
'\n'
623 if (tgtFunc.isExternal()) {
627 ->emitOpError(
"as argument to a no-body free function is not supported by this pass.")
628 .attachNote(inCall.getLoc())
629 .append(
"used by this call");
633 TypeSwitch<Operation *, MemberRefOpInterface>(origin)
634 .template Case<MemberReadOp>([](
auto p) {
return p; })
635 .
template Case<CreateStructOp>([](
auto p) {
636 return findOpThatStoresSubcmp(p, [&p]() {
return p.emitOpError(); }).value_or(
nullptr);
637 }).Default([](Operation *p) {
638 llvm::errs() <<
"Encountered unexpected op: "
639 << (p ? p->getName().getStringRef() :
"<<null>>") <<
'\n';
640 llvm_unreachable(
"Unexpected op kind");
644 llvm::dbgs() <<
"[DanglingUseHandler::handleUseInCallOp] member ref op for param: "
647 if (!paramFromMember) {
650 const SrcStructMemberToCloneInDest &newMembers =
651 destToSrcToClone.at(getDef(tables, paramFromMember));
653 llvm::dbgs() <<
"[DanglingUseHandler::handleUseInCallOp] members to split: "
658 splitFunctionParam(tgtFunc, argIdx, newMembers);
660 llvm::dbgs() <<
"[DanglingUseHandler::handleUseInCallOp] UPDATED call target: " << tgtFunc
662 llvm::dbgs() <<
"[DanglingUseHandler::handleUseInCallOp] UPDATED call target type: "
668 OpBuilder builder(inCall);
669 SmallVector<Value> splitArgs;
673 for (
auto [origName, newMemberRef] : newMembers) {
675 inCall.getLoc(), newMemberRef.getType(), originalBaseVal, newMemberRef.getNameAttr()
681 newOpArgs.erase(newOpArgs.begin() + argIdx), splitArgs.begin(), splitArgs.end()
684 inCall.replaceAllUsesWith(builder.create<
CallOp>(
690 llvm::dbgs() <<
"[DanglingUseHandler::handleUseInCallOp] UPDATED function: "
691 << origin->getParentOfType<
FuncDefOp>() <<
'\n';
697 inline bool opWillBeDeleted(Operation *otherOp)
const {
698 return std::apply([&](
const auto &...sets) {
699 return ((sets.contains(otherOp)) || ...);
700 }, otherRefsToBeDeleted);
707 static void splitFunctionParam(
708 FuncDefOp func,
unsigned paramIdx,
const SrcStructMemberToCloneInDest &nameToNewMember
712 const SrcStructMemberToCloneInDest &newMembers;
713 std::optional<std::string> originalArgName;
714 SmallVector<std::string> existingArgNames;
717 Impl(FuncDefOp func,
unsigned paramIdx,
const SrcStructMemberToCloneInDest &nameToNewMember)
718 : inputIdx(paramIdx), newMembers(nameToNewMember) {
719 for (
unsigned i = 0, e = func.getNumArguments(); i < e; ++i) {
720 if (std::optional<StringAttr> argName = func.getArgNameAttr(i)) {
721 existingArgNames.push_back(argName->getValue().str());
723 originalArgName = argName->getValue().str();
730 SmallVector<Type> convertInputs(ArrayRef<Type> origTypes)
override {
731 SmallVector<Type> newTypes(origTypes);
732 auto *it = newTypes.erase(newTypes.begin() + inputIdx);
733 for (
auto [_, newMember] : newMembers) {
734 newTypes.insert(it, newMember.getType());
739 SmallVector<Type> convertResults(ArrayRef<Type> origTypes)
override {
740 return SmallVector<Type>(origTypes);
742 ArrayAttr convertInputAttrs(ArrayAttr origAttrs, SmallVector<Type>)
override {
745 SmallVector<Attribute> newAttrs(origAttrs.getValue());
746 auto splitAttr = llvm::cast<DictionaryAttr>(origAttrs[inputIdx]);
747 SmallVector<Attribute> splitAttrs;
748 if (originalArgName) {
749 llvm::StringSet<> usedArgNames;
750 for (StringRef argName : existingArgNames) {
751 usedArgNames.insert(argName);
753 for (
auto [memberName, _] : newMembers) {
754 std::string desiredName = (*originalArgName +
'.' + memberName).str();
760 splitAttrs.append(newMembers.size(), splitAttr);
762 newAttrs[inputIdx] = splitAttrs.front();
764 newAttrs.begin() + inputIdx + 1, splitAttrs.begin() + 1, splitAttrs.end()
766 return ArrayAttr::get(origAttrs.getContext(), newAttrs);
770 ArrayAttr convertResultAttrs(ArrayAttr origAttrs, SmallVector<Type>)
override {
774 void processBlockArgs(Block &entryBlock, RewriterBase &rewriter)
override {
775 Value oldStructRef = entryBlock.getArgument(inputIdx);
779 llvm::StringMap<BlockArgument> memberNameToNewArg;
780 Location loc = oldStructRef.getLoc();
781 unsigned idx = inputIdx;
782 for (
auto [memberName, newMember] : newMembers) {
784 BlockArgument newArg = entryBlock.insertArgument(++idx, newMember.getType(), loc);
785 memberNameToNewArg[memberName] = newArg;
790 for (OpOperand &oldBlockArgUse : llvm::make_early_inc_range(oldStructRef.getUses())) {
791 if (
MemberReadOp readOp = llvm::dyn_cast<MemberReadOp>(oldBlockArgUse.getOwner())) {
793 BlockArgument newArg = memberNameToNewArg.at(readOp.
getMemberName());
794 rewriter.replaceAllUsesWith(readOp, newArg);
795 rewriter.eraseOp(readOp);
800 llvm::errs() <<
"Unexpected use of " << oldBlockArgUse.get() <<
" in "
801 << *oldBlockArgUse.getOwner() <<
'\n';
802 llvm_unreachable(
"Not yet implemented");
806 entryBlock.eraseArgument(inputIdx);
809 IRRewriter rewriter(func.getContext());
810 Impl(func, paramIdx, nameToNewMember).convert(func, rewriter);
817static LogicalResult finalizeStruct(
818 SymbolTableCollection &tables,
StructDefOp caller, PendingErasure &&toDelete,
819 DestToSrcToClonedSrcInDest &&destToSrcToClone
822 llvm::dbgs() <<
"[finalizeStruct] dumping 'caller' struct before compressing chains:\n";
823 caller.
print(llvm::dbgs(), OpPrintingFlags().assumeVerified());
824 llvm::dbgs() <<
'\n';
829 combineReadChain(readOp, tables, destToSrcToClone);
833 auto res = computeFn.walk([&tables, &destToSrcToClone, &computeSelfVal](
MemberReadOp readOp) {
834 combineReadChain(readOp, tables, destToSrcToClone);
838 return WalkResult::advance();
840 LogicalResult innerRes = combineNewThenReadChain(readOp, tables, destToSrcToClone);
841 return failed(innerRes) ? WalkResult::interrupt() : WalkResult::advance();
843 if (res.wasInterrupted()) {
848 llvm::dbgs() <<
"[finalizeStruct] dumping 'caller' struct before deleting ops:\n";
849 caller.
print(llvm::dbgs(), OpPrintingFlags().assumeVerified());
850 llvm::dbgs() <<
'\n';
851 llvm::dbgs() <<
"[finalizeStruct] ops marked for deletion:\n";
852 for (Operation *op : toDelete.memberReadOps) {
853 llvm::dbgs().indent(2) << *op <<
'\n';
855 for (Operation *op : toDelete.memberWriteOps) {
856 llvm::dbgs().indent(2) << *op <<
'\n';
859 llvm::dbgs().indent(2) << op <<
'\n';
861 for (DestMemberWithSrcStructType op : toDelete.memberDefs) {
862 llvm::dbgs().indent(2) << op <<
'\n';
868 DanglingUseHandler<SmallPtrSet<Operation *, 8>, SmallPtrSet<Operation *, 8>> useHandler(
869 tables, destToSrcToClone, toDelete.memberWriteOps, toDelete.memberReadOps
872 if (failed(useHandler.handle(op))) {
878 for (Operation *op : toDelete.memberWriteOps) {
879 if (failed(useHandler.handle(op))) {
884 for (Operation *op : toDelete.memberReadOps) {
885 if (failed(useHandler.handle(op))) {
894 SymbolTable &callerSymTab = tables.getSymbolTable(caller);
895 for (DestMemberWithSrcStructType op : toDelete.memberDefs) {
896 assert(op.getParentOp() == caller);
897 callerSymTab.erase(op);
908 for (
auto &[caller, callees] : plan) {
911 PendingErasure toDelete;
913 DestToSrcToClonedSrcInDest aggregateReplacements;
916 FailureOr<DestToSrcToClonedSrcInDest> res =
917 StructInliner(tables, toDelete, toInline, caller).doInline();
922 for (
auto &[k, v] : res.value()) {
923 assert(!aggregateReplacements.contains(k) &&
"duplicate not possible");
924 aggregateReplacements[k] = std::move(v);
928 LogicalResult finalizeResult =
929 finalizeStruct(tables, caller, std::move(toDelete), std::move(aggregateReplacements));
930 if (failed(finalizeResult)) {
940 using Base = InlineStructsPassBase<PassImpl>;
943 static uint64_t complexity(FuncDefOp f) {
944 uint64_t complexity = 0;
945 f.
getBody().walk([&complexity](Operation *op) {
946 if (llvm::isa<felt::MulFeltOp>(op)) {
948 }
else if (
auto ee = llvm::dyn_cast<constrain::EmitEqualityOp>(op)) {
950 }
else if (
auto ec = llvm::dyn_cast<constrain::EmitContainmentOp>(op)) {
965 getIfResolvableStructConstrain(
const SymbolUseGraphNode *node, SymbolTableCollection &tables) {
970 if (failed(lookupRes)) {
973 FuncDefOp func = llvm::dyn_cast<FuncDefOp>(lookupRes->get());
982 static inline StructDefOp getParentStruct(FuncDefOp func) {
985 assert(currentNodeParentStruct);
986 return currentNodeParentStruct;
990 inline bool exceedsMaxComplexity(uint64_t
check) {
991 return maxComplexity > 0 &&
check > maxComplexity;
996 static inline bool canInline(FuncDefOp currentFunc, FuncDefOp successorFunc) {
1008 WalkResult res = currentFunc.walk([](CallOp c) {
1009 return getMemberReadThatDefinesSelfValuePassedToConstrain(c)
1010 ? WalkResult::interrupt()
1011 : WalkResult::advance();
1017 return res.wasInterrupted();
1022 static LogicalResult
1023 verifyNoTemplateSymbolBindings(
const SymbolUseGraph &useGraph, SymbolTableCollection &tables) {
1024 for (
const SymbolUseGraphNode *node : useGraph.
nodesIter()) {
1034 Operation *reportLoc = succeeded(res) ? res->get() : lookupFrom;
1035 return reportLoc->emitError() <<
"Cannot inline struct within a template. Run "
1036 "`llzk-flatten` to instantiate templated structs.";
1043 static LogicalResult emitConstrainReachableCycleError(
1044 ArrayRef<const SymbolUseGraphNode *> dfsStack,
const SymbolUseGraphNode *cycleHead,
1045 SymbolTableCollection &tables
1047 SmallVector<const SymbolUseGraphNode *, 8> cycle;
1048 bool inCycle =
false;
1049 for (
const SymbolUseGraphNode *node : dfsStack) {
1050 if (node == cycleHead) {
1054 cycle.push_back(node);
1057 if (cycle.empty()) {
1058 cycle.push_back(cycleHead);
1062 for (
const SymbolUseGraphNode *node : cycle) {
1067 if (failed(lookupRes)) {
1070 Operation *op = lookupRes->get();
1072 if (llvm::isa<FuncDefOp>(op)) {
1077 InFlightDiagnostic diag = reportOp->emitError();
1078 diag <<
"Cannot inline structs when a symbol-use cycle is reachable from a struct "
1079 "\"@constrain\" function. Prover-side recursion is allowed only when "
1080 "\"@constrain\" cannot reach it.";
1082 for (
const SymbolUseGraphNode *node : cycle) {
1086 if (
auto lookupRes = node->
lookupSymbol(tables,
false);
1087 succeeded(lookupRes)) {
1088 diag.attachNote(lookupRes->get()->getLoc()) <<
"cycle contains " << node->
getSymbolPath();
1103 static LogicalResult computeConstrainReachablePostOrder(
1104 const SymbolUseGraph &useGraph, SymbolTableCollection &tables,
1105 SmallVectorImpl<const SymbolUseGraphNode *> &postOrder
1107 enum class VisitState : std::uint8_t { Active, Done };
1109 DenseMap<const SymbolUseGraphNode *, VisitState> state;
1110 SmallVector<const SymbolUseGraphNode *, 32> dfsStack;
1112 auto dfs = [&](
auto &&self,
const SymbolUseGraphNode *node) -> LogicalResult {
1113 auto seen = state.find(node);
1114 if (seen != state.end()) {
1115 if (seen->second == VisitState::Active) {
1116 return emitConstrainReachableCycleError(dfsStack, node, tables);
1121 state[node] = VisitState::Active;
1122 dfsStack.push_back(node);
1123 for (
const SymbolUseGraphNode *successor : node->
successorIter()) {
1124 if (failed(self(self, successor))) {
1128 dfsStack.pop_back();
1130 state[node] = VisitState::Done;
1131 postOrder.push_back(node);
1135 for (
const SymbolUseGraphNode *node : useGraph.
nodesIter()) {
1136 if (!getIfResolvableStructConstrain(node, tables)) {
1139 if (failed(dfs(dfs, node))) {
1151 inline FailureOr<InliningPlan>
1152 makePlan(
const SymbolUseGraph &useGraph, SymbolTableCollection &tables) {
1154 llvm::dbgs() <<
"Running InlineStructsPass with max complexity ";
1155 if (maxComplexity == 0) {
1156 llvm::dbgs() <<
"unlimited";
1158 llvm::dbgs() << maxComplexity;
1160 llvm::dbgs() <<
'\n';
1163 DenseMap<const SymbolUseGraphNode *, uint64_t> complexityMemo;
1165 if (failed(verifyNoTemplateSymbolBindings(useGraph, tables))) {
1169 SmallVector<const SymbolUseGraphNode *, 32> constrainPostOrder;
1170 if (failed(computeConstrainReachablePostOrder(useGraph, tables, constrainPostOrder))) {
1177 for (
const SymbolUseGraphNode *currentNode : constrainPostOrder) {
1178 LLVM_DEBUG(llvm::dbgs() <<
"\ncurrentNode = " << currentNode->toString());
1179 FuncDefOp currentFunc = getIfResolvableStructConstrain(currentNode, tables);
1183 uint64_t currentComplexity = complexity(currentFunc);
1185 if (exceedsMaxComplexity(currentComplexity)) {
1186 complexityMemo[currentNode] = currentComplexity;
1191 SmallVector<StructDefOp> successorsToMerge;
1192 for (
const SymbolUseGraphNode *successor : currentNode->successorIter()) {
1193 LLVM_DEBUG(llvm::dbgs().indent(2) <<
"successor: " << successor->toString() <<
'\n');
1195 auto memoResult = complexityMemo.find(successor);
1196 if (memoResult == complexityMemo.end()) {
1199 uint64_t sComplexity = memoResult->second;
1201 sComplexity <= (std::numeric_limits<uint64_t>::max() - currentComplexity) &&
1202 "addition will overflow"
1204 uint64_t potentialComplexity = currentComplexity + sComplexity;
1205 if (!exceedsMaxComplexity(potentialComplexity)) {
1206 currentComplexity = potentialComplexity;
1207 FuncDefOp successorFunc = getIfResolvableStructConstrain(successor, tables);
1208 if (!successorFunc) {
1211 if (canInline(currentFunc, successorFunc)) {
1212 successorsToMerge.push_back(getParentStruct(successorFunc));
1216 complexityMemo[currentNode] = currentComplexity;
1217 if (!successorsToMerge.empty()) {
1218 retVal.emplace_back(getParentStruct(currentFunc), std::move(successorsToMerge));
1222 llvm::dbgs() <<
"-----------------------------------------------------------------\n";
1223 llvm::dbgs() <<
"InlineStructsPass plan:\n";
1224 for (
auto &[caller, callees] : retVal) {
1225 llvm::dbgs().indent(2) <<
"inlining the following into \"" << caller.
getSymName() <<
"\"\n";
1226 for (StructDefOp c : callees) {
1227 llvm::dbgs().indent(4) <<
"\"" << c.getSymName() <<
"\"\n";
1230 llvm::dbgs() <<
"-----------------------------------------------------------------\n";
1236 void runOnOperation()
override {
1237 const SymbolUseGraph &useGraph = getAnalysis<SymbolUseGraph>();
1240 SymbolTableCollection tables;
1241 FailureOr<InliningPlan> plan = makePlan(useGraph, tables);
1243 signalPassFailure();
1248 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()
::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()
::mlir::Value getSelfValueFromConstrain()
Return the "self" value (i.e.
::mlir::OperandRangeRange getMapOperands()
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 ...
::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.