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 matchAndRewrite(MemberRefOpInterface op, PatternRewriter &rewriter)
const final {
274 if (op.getComponent() != oldBaseVal || !oldToNewMembers.contains(op.getMemberName())) {
277 rewriter.modifyOpInPlace(op, [
this, &op]() {
278 DestCloneOfSrcStructMember newF = oldToNewMembers.at(op.getMemberName());
279 op.setMemberName(newF.getSymName());
280 op.getComponentMutable().set(this->newBaseVal);
287 static FuncDefOp cloneWithMemberRefUpdate(std::unique_ptr<MemberRefRewriter> thisPat) {
289 FuncDefOp srcFuncClone = thisPat->funcRef.clone(mapper);
291 thisPat->funcRef = srcFuncClone;
292 thisPat->oldBaseVal = getSelfValue(srcFuncClone);
294 MLIRContext *ctx = thisPat->getContext();
295 RewritePatternSet patterns(ctx, std::move(thisPat));
296 walkAndApplyPatterns(srcFuncClone, std::move(patterns));
305 const StructInliner &data;
306 const DestToSrcToClonedSrcInDest &destToSrcToClone;
310 virtual MemberRefOpInterface getSelfRefMember(CallOp callOp) = 0;
311 virtual void processCloneBeforeInlining(FuncDefOp func) {}
312 virtual ~ImplBase() =
default;
315 ImplBase(
const StructInliner &inliner,
const DestToSrcToClonedSrcInDest &destToSrcToCloneRef)
316 : data(inliner), destToSrcToClone(destToSrcToCloneRef) {}
318 LogicalResult doInlining(FuncDefOp srcFunc, FuncDefOp destFunc) {
320 llvm::dbgs() <<
"[doInlining] SOURCE FUNCTION:\n";
322 llvm::dbgs() <<
"[doInlining] DESTINATION FUNCTION:\n";
326 InlinerInterface inliner(destFunc.getContext());
329 auto callHandler = [
this, &inliner, &srcFunc](CallOp callOp) {
332 assert(succeeded(callOpTarget));
333 if (callOpTarget->get() != srcFunc) {
334 return WalkResult::advance();
339 MemberRefOpInterface selfMemberRefOp = this->getSelfRefMember(callOp);
340 if (!selfMemberRefOp) {
342 return WalkResult::interrupt();
348 FuncDefOp srcFuncClone = MemberRefRewriter::cloneWithMemberRefUpdate(
349 std::make_unique<MemberRefRewriter>(
351 this->destToSrcToClone.at(this->data.getDef(selfMemberRefOp))
354 this->processCloneBeforeInlining(srcFuncClone);
357 LogicalResult inlineCallRes =
358 inlineCall(inliner, callOp, srcFuncClone, &srcFuncClone.
getBody(),
false);
359 if (failed(inlineCallRes)) {
361 return WalkResult::interrupt();
363 srcFuncClone.erase();
365 return WalkResult::skip();
368 auto memberWriteHandler = [
this](MemberWriteOp writeOp) {
370 if (this->destToSrcToClone.contains(this->data.getDef(writeOp))) {
371 this->data.toDelete.memberWriteOps.insert(writeOp);
373 return WalkResult::advance();
378 auto memberReadHandler = [
this](MemberReadOp readOp) {
380 if (combineReadChain(readOp, this->data.tables, destToSrcToClone)) {
381 return WalkResult::skip();
383 if (this->destToSrcToClone.contains(this->data.getDef(readOp))) {
384 this->data.toDelete.memberReadOps.insert(readOp);
386 return WalkResult::advance();
389 WalkResult walkRes = destFunc.
getBody().walk<WalkOrder::PreOrder>([&](Operation *op) {
390 return TypeSwitch<Operation *, WalkResult>(op)
391 .Case<CallOp>(callHandler)
392 .Case<MemberWriteOp>(memberWriteHandler)
393 .Case<MemberReadOp>(memberReadHandler)
394 .Default([](Operation *) {
return WalkResult::advance(); });
397 return failure(walkRes.wasInterrupted());
401 class ConstrainImpl :
public ImplBase {
402 using ImplBase::ImplBase;
404 MemberRefOpInterface getSelfRefMember(CallOp callOp)
override {
405 LLVM_DEBUG({ llvm::dbgs() <<
"[ConstrainImpl::getSelfRefMember] " << callOp <<
'\n'; });
410 MemberRefOpInterface selfMemberRef =
411 getMemberReadThatDefinesSelfValuePassedToConstrain(callOp);
413 selfMemberRef.getComponent().getType() == this->data.destStruct.getType()) {
414 return selfMemberRef;
419 "\" to be passed a value read from a member in the current stuct."
426 class ComputeImpl :
public ImplBase {
427 using ImplBase::ImplBase;
429 MemberRefOpInterface getSelfRefMember(CallOp callOp)
override {
430 LLVM_DEBUG({ llvm::dbgs() <<
"[ComputeImpl::getSelfRefMember] " << callOp <<
'\n'; });
437 FailureOr<MemberWriteOp> foundWrite =
439 return callOp.emitOpError().append(
"\"@", FUNC_NAME_COMPUTE,
"\" ");
441 return static_cast<MemberRefOpInterface
>(foundWrite.value_or(
nullptr));
444 void processCloneBeforeInlining(FuncDefOp func)
override {
448 func.
getBody().walk([
this](CreateStructOp newStructOp) {
449 if (newStructOp.getType() == this->data.srcStruct.getType()) {
450 this->data.toDelete.newStructOps.push_back(newStructOp);
459 DestToSrcToClonedSrcInDest cloneMembers() {
460 DestToSrcToClonedSrcInDest destToSrcToClone;
462 SymbolTable &destStructSymTable = tables.getSymbolTable(destStruct);
463 StructType srcStructType = srcStruct.getType();
464 for (MemberDefOp destMember : destStruct.getMemberDefs()) {
465 if (StructType destMemberType = llvm::dyn_cast<StructType>(destMember.getType())) {
470 assert(unifications.empty());
472 toDelete.memberDefs.push_back(destMember);
475 SrcStructMemberToCloneInDest &srcToClone = destToSrcToClone[destMember];
476 std::vector<MemberDefOp> srcMembers = srcStruct.getMemberDefs();
477 if (srcMembers.empty()) {
480 OpBuilder builder(destMember);
481 std::string newNameBase =
483 for (MemberDefOp srcMember : srcMembers) {
484 DestCloneOfSrcStructMember newF = llvm::cast<MemberDefOp>(builder.clone(*srcMember));
485 newF.setName(builder.getStringAttr(newNameBase +
'+' + newF.getName()));
486 srcToClone[srcMember.getSymNameAttr()] = newF;
488 destStructSymTable.insert(newF);
492 return destToSrcToClone;
496 inline LogicalResult inlineConstrainCall(
const DestToSrcToClonedSrcInDest &destToSrcToClone) {
497 return ConstrainImpl(*
this, destToSrcToClone)
498 .doInlining(srcStruct.getConstrainFuncOp(), destStruct.getConstrainFuncOp());
502 inline LogicalResult inlineComputeCall(
const DestToSrcToClonedSrcInDest &destToSrcToClone) {
503 return ComputeImpl(*
this, destToSrcToClone)
504 .doInlining(srcStruct.getComputeFuncOp(), destStruct.getComputeFuncOp());
509 SymbolTableCollection &tbls, PendingErasure &opsToDelete, StructDefOp
from, StructDefOp into
511 : tables(tbls), toDelete(opsToDelete), srcStruct(
from), destStruct(into) {}
513 FailureOr<DestToSrcToClonedSrcInDest> doInline() {
515 llvm::dbgs() <<
"[StructInliner] merge " << srcStruct.getSymNameAttr() <<
" into "
516 << destStruct.getSymNameAttr() <<
'\n'
519 DestToSrcToClonedSrcInDest destToSrcToClone = cloneMembers();
520 if (failed(inlineConstrainCall(destToSrcToClone)) ||
521 failed(inlineComputeCall(destToSrcToClone))) {
524 return destToSrcToClone;
530 { t.contains(p) } -> std::convertible_to<bool>;
534template <
typename... PendingDeletionSets>
536class DanglingUseHandler {
537 SymbolTableCollection &tables;
538 const DestToSrcToClonedSrcInDest &destToSrcToClone;
539 std::tuple<
const PendingDeletionSets &...> otherRefsToBeDeleted;
543 SymbolTableCollection &symTables,
const DestToSrcToClonedSrcInDest &destToSrcToCloneRef,
544 const PendingDeletionSets &...otherRefsPendingDeletion
546 : tables(symTables), destToSrcToClone(destToSrcToCloneRef),
547 otherRefsToBeDeleted(otherRefsPendingDeletion...) {}
554 LogicalResult handle(Operation *op)
const {
555 if (op->use_empty()) {
560 llvm::dbgs() <<
"[DanglingUseHandler::handle] op: " << *op <<
'\n';
561 llvm::dbgs() <<
"[DanglingUseHandler::handle] in function: "
562 << op->getParentOfType<
FuncDefOp>() <<
'\n';
564 for (OpOperand &
use : llvm::make_early_inc_range(op->getUses())) {
565 if (
CallOp c = llvm::dyn_cast<CallOp>(
use.getOwner())) {
566 if (failed(handleUseInCallOp(
use, c, op))) {
570 Operation *user =
use.getOwner();
572 if (!opWillBeDeleted(user)) {
573 return op->emitOpError()
575 "with use in '", user->getName().getStringRef(),
576 "' is not (currently) supported by this pass."
578 .attachNote(user->getLoc())
579 .append(
"used by this operation");
584 if (!op->use_empty()) {
585 for (Operation *user : op->getUsers()) {
586 if (!opWillBeDeleted(user)) {
587 llvm::errs() <<
"Op has remaining use(s) that could not be removed: " << *op <<
'\n';
588 llvm_unreachable(
"Expected all uses to be removed");
601 inline LogicalResult handleUseInCallOp(OpOperand &
use,
CallOp inCall, Operation *origin)
const {
603 llvm::dbgs() <<
"[DanglingUseHandler::handleUseInCallOp] use in call: " << inCall <<
'\n'
605 unsigned argIdx =
use.getOperandNumber() - inCall.
getArgOperands().getBeginOperandIndex();
607 llvm::dbgs() <<
"[DanglingUseHandler::handleUseInCallOp] at index: " << argIdx <<
'\n'
611 if (failed(tgtFuncRes)) {
613 ->emitOpError(
"as argument to an unknown function is not supported by this pass.")
614 .attachNote(inCall.getLoc())
615 .append(
"used by this call");
619 llvm::dbgs() <<
"[DanglingUseHandler::handleUseInCallOp] call target: " << tgtFunc <<
'\n'
621 if (tgtFunc.isExternal()) {
625 ->emitOpError(
"as argument to a no-body free function is not supported by this pass.")
626 .attachNote(inCall.getLoc())
627 .append(
"used by this call");
631 TypeSwitch<Operation *, MemberRefOpInterface>(origin)
632 .template Case<MemberReadOp>([](
auto p) {
return p; })
633 .
template Case<CreateStructOp>([](
auto p) {
634 return findOpThatStoresSubcmp(p, [&p]() {
return p.emitOpError(); }).value_or(
nullptr);
635 }).Default([](Operation *p) {
636 llvm::errs() <<
"Encountered unexpected op: "
637 << (p ? p->getName().getStringRef() :
"<<null>>") <<
'\n';
638 llvm_unreachable(
"Unexpected op kind");
642 llvm::dbgs() <<
"[DanglingUseHandler::handleUseInCallOp] member ref op for param: "
645 if (!paramFromMember) {
648 const SrcStructMemberToCloneInDest &newMembers =
649 destToSrcToClone.at(getDef(tables, paramFromMember));
651 llvm::dbgs() <<
"[DanglingUseHandler::handleUseInCallOp] members to split: "
656 splitFunctionParam(tgtFunc, argIdx, newMembers);
658 llvm::dbgs() <<
"[DanglingUseHandler::handleUseInCallOp] UPDATED call target: " << tgtFunc
660 llvm::dbgs() <<
"[DanglingUseHandler::handleUseInCallOp] UPDATED call target type: "
666 OpBuilder builder(inCall);
667 SmallVector<Value> splitArgs;
671 for (
auto [origName, newMemberRef] : newMembers) {
673 inCall.getLoc(), newMemberRef.getType(), originalBaseVal, newMemberRef.getNameAttr()
679 newOpArgs.erase(newOpArgs.begin() + argIdx), splitArgs.begin(), splitArgs.end()
682 inCall.replaceAllUsesWith(builder.create<
CallOp>(
688 llvm::dbgs() <<
"[DanglingUseHandler::handleUseInCallOp] UPDATED function: "
689 << origin->getParentOfType<
FuncDefOp>() <<
'\n';
695 inline bool opWillBeDeleted(Operation *otherOp)
const {
696 return std::apply([&](
const auto &...sets) {
697 return ((sets.contains(otherOp)) || ...);
698 }, otherRefsToBeDeleted);
705 static void splitFunctionParam(
706 FuncDefOp func,
unsigned paramIdx,
const SrcStructMemberToCloneInDest &nameToNewMember
710 const SrcStructMemberToCloneInDest &newMembers;
711 std::optional<std::string> originalArgName;
712 SmallVector<std::string> existingArgNames;
715 Impl(FuncDefOp func,
unsigned paramIdx,
const SrcStructMemberToCloneInDest &nameToNewMember)
716 : inputIdx(paramIdx), newMembers(nameToNewMember) {
717 for (
unsigned i = 0, e = func.getNumArguments(); i < e; ++i) {
718 if (std::optional<StringAttr> argName = func.getArgNameAttr(i)) {
719 existingArgNames.push_back(argName->getValue().str());
721 originalArgName = argName->getValue().str();
728 SmallVector<Type> convertInputs(ArrayRef<Type> origTypes)
override {
729 SmallVector<Type> newTypes(origTypes);
730 auto *it = newTypes.erase(newTypes.begin() + inputIdx);
731 for (
auto [_, newMember] : newMembers) {
732 newTypes.insert(it, newMember.getType());
737 SmallVector<Type> convertResults(ArrayRef<Type> origTypes)
override {
738 return SmallVector<Type>(origTypes);
740 ArrayAttr convertInputAttrs(ArrayAttr origAttrs, SmallVector<Type>)
override {
743 SmallVector<Attribute> newAttrs(origAttrs.getValue());
744 auto splitAttr = llvm::cast<DictionaryAttr>(origAttrs[inputIdx]);
745 SmallVector<Attribute> splitAttrs;
746 if (originalArgName) {
747 llvm::StringSet<> usedArgNames;
748 for (StringRef argName : existingArgNames) {
749 usedArgNames.insert(argName);
751 for (
auto [memberName, _] : newMembers) {
752 std::string desiredName = (*originalArgName +
'.' + memberName).str();
758 splitAttrs.append(newMembers.size(), splitAttr);
760 newAttrs[inputIdx] = splitAttrs.front();
762 newAttrs.begin() + inputIdx + 1, splitAttrs.begin() + 1, splitAttrs.end()
764 return ArrayAttr::get(origAttrs.getContext(), newAttrs);
768 ArrayAttr convertResultAttrs(ArrayAttr origAttrs, SmallVector<Type>)
override {
772 void processBlockArgs(Block &entryBlock, RewriterBase &rewriter)
override {
773 Value oldStructRef = entryBlock.getArgument(inputIdx);
777 llvm::StringMap<BlockArgument> memberNameToNewArg;
778 Location loc = oldStructRef.getLoc();
779 unsigned idx = inputIdx;
780 for (
auto [memberName, newMember] : newMembers) {
782 BlockArgument newArg = entryBlock.insertArgument(++idx, newMember.getType(), loc);
783 memberNameToNewArg[memberName] = newArg;
788 for (OpOperand &oldBlockArgUse : llvm::make_early_inc_range(oldStructRef.getUses())) {
789 if (
MemberReadOp readOp = llvm::dyn_cast<MemberReadOp>(oldBlockArgUse.getOwner())) {
791 BlockArgument newArg = memberNameToNewArg.at(readOp.
getMemberName());
792 rewriter.replaceAllUsesWith(readOp, newArg);
793 rewriter.eraseOp(readOp);
798 llvm::errs() <<
"Unexpected use of " << oldBlockArgUse.get() <<
" in "
799 << *oldBlockArgUse.getOwner() <<
'\n';
800 llvm_unreachable(
"Not yet implemented");
804 entryBlock.eraseArgument(inputIdx);
807 IRRewriter rewriter(func.getContext());
808 Impl(func, paramIdx, nameToNewMember).convert(func, rewriter);
815static LogicalResult finalizeStruct(
816 SymbolTableCollection &tables,
StructDefOp caller, PendingErasure &&toDelete,
817 DestToSrcToClonedSrcInDest &&destToSrcToClone
820 llvm::dbgs() <<
"[finalizeStruct] dumping 'caller' struct before compressing chains:\n";
821 caller.
print(llvm::dbgs(), OpPrintingFlags().assumeVerified());
822 llvm::dbgs() <<
'\n';
827 combineReadChain(readOp, tables, destToSrcToClone);
831 auto res = computeFn.walk([&tables, &destToSrcToClone, &computeSelfVal](
MemberReadOp readOp) {
832 combineReadChain(readOp, tables, destToSrcToClone);
836 return WalkResult::advance();
838 return WalkResult(combineNewThenReadChain(readOp, tables, destToSrcToClone));
840 if (res.wasInterrupted()) {
845 llvm::dbgs() <<
"[finalizeStruct] dumping 'caller' struct before deleting ops:\n";
846 caller.
print(llvm::dbgs(), OpPrintingFlags().assumeVerified());
847 llvm::dbgs() <<
'\n';
848 llvm::dbgs() <<
"[finalizeStruct] ops marked for deletion:\n";
849 for (Operation *op : toDelete.memberReadOps) {
850 llvm::dbgs().indent(2) << *op <<
'\n';
852 for (Operation *op : toDelete.memberWriteOps) {
853 llvm::dbgs().indent(2) << *op <<
'\n';
856 llvm::dbgs().indent(2) << op <<
'\n';
858 for (DestMemberWithSrcStructType op : toDelete.memberDefs) {
859 llvm::dbgs().indent(2) << op <<
'\n';
865 DanglingUseHandler<SmallPtrSet<Operation *, 8>, SmallPtrSet<Operation *, 8>> useHandler(
866 tables, destToSrcToClone, toDelete.memberWriteOps, toDelete.memberReadOps
869 if (failed(useHandler.handle(op))) {
875 for (Operation *op : toDelete.memberWriteOps) {
876 if (failed(useHandler.handle(op))) {
881 for (Operation *op : toDelete.memberReadOps) {
882 if (failed(useHandler.handle(op))) {
891 SymbolTable &callerSymTab = tables.getSymbolTable(caller);
892 for (DestMemberWithSrcStructType op : toDelete.memberDefs) {
893 assert(op.getParentOp() == caller);
894 callerSymTab.erase(op);
905 for (
auto &[caller, callees] : plan) {
908 PendingErasure toDelete;
910 DestToSrcToClonedSrcInDest aggregateReplacements;
913 FailureOr<DestToSrcToClonedSrcInDest> res =
914 StructInliner(tables, toDelete, toInline, caller).doInline();
919 for (
auto &[k, v] : res.value()) {
920 assert(!aggregateReplacements.contains(k) &&
"duplicate not possible");
921 aggregateReplacements[k] = std::move(v);
925 LogicalResult finalizeResult =
926 finalizeStruct(tables, caller, std::move(toDelete), std::move(aggregateReplacements));
927 if (failed(finalizeResult)) {
937 using Base = InlineStructsPassBase<PassImpl>;
940 static uint64_t complexity(FuncDefOp f) {
941 uint64_t complexity = 0;
942 f.
getBody().walk([&complexity](Operation *op) {
943 if (llvm::isa<felt::MulFeltOp>(op)) {
945 }
else if (
auto ee = llvm::dyn_cast<constrain::EmitEqualityOp>(op)) {
947 }
else if (
auto ec = llvm::dyn_cast<constrain::EmitContainmentOp>(op)) {
962 getIfResolvableStructConstrain(
const SymbolUseGraphNode *node, SymbolTableCollection &tables) {
967 if (failed(lookupRes)) {
970 FuncDefOp func = llvm::dyn_cast<FuncDefOp>(lookupRes->get());
979 static inline StructDefOp getParentStruct(FuncDefOp func) {
982 assert(currentNodeParentStruct);
983 return currentNodeParentStruct;
987 inline bool exceedsMaxComplexity(uint64_t
check) {
988 return maxComplexity > 0 &&
check > maxComplexity;
993 static inline bool canInline(FuncDefOp currentFunc, FuncDefOp successorFunc) {
1005 WalkResult res = currentFunc.walk([](CallOp c) {
1006 return getMemberReadThatDefinesSelfValuePassedToConstrain(c)
1007 ? WalkResult::interrupt()
1008 : WalkResult::advance();
1014 return res.wasInterrupted();
1019 static LogicalResult
1020 verifyNoTemplateSymbolBindings(
const SymbolUseGraph &useGraph, SymbolTableCollection &tables) {
1021 for (
const SymbolUseGraphNode *node : useGraph.
nodesIter()) {
1031 Operation *reportLoc = succeeded(res) ? res->get() : lookupFrom;
1032 return reportLoc->emitError() <<
"Cannot inline struct within a template. Run "
1033 "`llzk-flatten` to instantiate templated structs.";
1040 static LogicalResult emitConstrainReachableCycleError(
1041 ArrayRef<const SymbolUseGraphNode *> dfsStack,
const SymbolUseGraphNode *cycleHead,
1042 SymbolTableCollection &tables
1044 SmallVector<const SymbolUseGraphNode *, 8> cycle;
1045 bool inCycle =
false;
1046 for (
const SymbolUseGraphNode *node : dfsStack) {
1047 if (node == cycleHead) {
1051 cycle.push_back(node);
1054 if (cycle.empty()) {
1055 cycle.push_back(cycleHead);
1059 for (
const SymbolUseGraphNode *node : cycle) {
1064 if (failed(lookupRes)) {
1067 Operation *op = lookupRes->get();
1069 if (llvm::isa<FuncDefOp>(op)) {
1074 InFlightDiagnostic diag = reportOp->emitError();
1075 diag <<
"Cannot inline structs when a symbol-use cycle is reachable from a struct "
1076 "\"@constrain\" function. Prover-side recursion is allowed only when "
1077 "\"@constrain\" cannot reach it.";
1079 for (
const SymbolUseGraphNode *node : cycle) {
1083 if (
auto lookupRes = node->
lookupSymbol(tables,
false);
1084 succeeded(lookupRes)) {
1085 diag.attachNote(lookupRes->get()->getLoc()) <<
"cycle contains " << node->
getSymbolPath();
1100 static LogicalResult computeConstrainReachablePostOrder(
1101 const SymbolUseGraph &useGraph, SymbolTableCollection &tables,
1102 SmallVectorImpl<const SymbolUseGraphNode *> &postOrder
1104 enum class VisitState : std::uint8_t { Active, Done };
1106 DenseMap<const SymbolUseGraphNode *, VisitState> state;
1107 SmallVector<const SymbolUseGraphNode *, 32> dfsStack;
1109 auto dfs = [&](
auto &&self,
const SymbolUseGraphNode *node) -> LogicalResult {
1110 auto seen = state.find(node);
1111 if (seen != state.end()) {
1112 if (seen->second == VisitState::Active) {
1113 return emitConstrainReachableCycleError(dfsStack, node, tables);
1118 state[node] = VisitState::Active;
1119 dfsStack.push_back(node);
1120 for (
const SymbolUseGraphNode *successor : node->
successorIter()) {
1121 if (failed(self(self, successor))) {
1125 dfsStack.pop_back();
1127 state[node] = VisitState::Done;
1128 postOrder.push_back(node);
1132 for (
const SymbolUseGraphNode *node : useGraph.
nodesIter()) {
1133 if (!getIfResolvableStructConstrain(node, tables)) {
1136 if (failed(dfs(dfs, node))) {
1148 inline FailureOr<InliningPlan>
1149 makePlan(
const SymbolUseGraph &useGraph, SymbolTableCollection &tables) {
1151 llvm::dbgs() <<
"Running InlineStructsPass with max complexity ";
1152 if (maxComplexity == 0) {
1153 llvm::dbgs() <<
"unlimited";
1155 llvm::dbgs() << maxComplexity;
1157 llvm::dbgs() <<
'\n';
1160 DenseMap<const SymbolUseGraphNode *, uint64_t> complexityMemo;
1162 if (failed(verifyNoTemplateSymbolBindings(useGraph, tables))) {
1166 SmallVector<const SymbolUseGraphNode *, 32> constrainPostOrder;
1167 if (failed(computeConstrainReachablePostOrder(useGraph, tables, constrainPostOrder))) {
1174 for (
const SymbolUseGraphNode *currentNode : constrainPostOrder) {
1175 LLVM_DEBUG(llvm::dbgs() <<
"\ncurrentNode = " << currentNode->toString());
1176 FuncDefOp currentFunc = getIfResolvableStructConstrain(currentNode, tables);
1180 uint64_t currentComplexity = complexity(currentFunc);
1182 if (exceedsMaxComplexity(currentComplexity)) {
1183 complexityMemo[currentNode] = currentComplexity;
1188 SmallVector<StructDefOp> successorsToMerge;
1189 for (
const SymbolUseGraphNode *successor : currentNode->successorIter()) {
1190 LLVM_DEBUG(llvm::dbgs().indent(2) <<
"successor: " << successor->toString() <<
'\n');
1192 auto memoResult = complexityMemo.find(successor);
1193 if (memoResult == complexityMemo.end()) {
1196 uint64_t sComplexity = memoResult->second;
1198 sComplexity <= (std::numeric_limits<uint64_t>::max() - currentComplexity) &&
1199 "addition will overflow"
1201 uint64_t potentialComplexity = currentComplexity + sComplexity;
1202 if (!exceedsMaxComplexity(potentialComplexity)) {
1203 currentComplexity = potentialComplexity;
1204 FuncDefOp successorFunc = getIfResolvableStructConstrain(successor, tables);
1205 if (!successorFunc) {
1208 if (canInline(currentFunc, successorFunc)) {
1209 successorsToMerge.push_back(getParentStruct(successorFunc));
1213 complexityMemo[currentNode] = currentComplexity;
1214 if (!successorsToMerge.empty()) {
1215 retVal.emplace_back(getParentStruct(currentFunc), std::move(successorsToMerge));
1219 llvm::dbgs() <<
"-----------------------------------------------------------------\n";
1220 llvm::dbgs() <<
"InlineStructsPass plan:\n";
1221 for (
auto &[caller, callees] : retVal) {
1222 llvm::dbgs().indent(2) <<
"inlining the following into \"" << caller.
getSymName() <<
"\"\n";
1223 for (StructDefOp c : callees) {
1224 llvm::dbgs().indent(4) <<
"\"" << c.getSymName() <<
"\"\n";
1227 llvm::dbgs() <<
"-----------------------------------------------------------------\n";
1233 void runOnOperation()
override {
1234 const SymbolUseGraph &useGraph = getAnalysis<SymbolUseGraph>();
1237 SymbolTableCollection tables;
1238 FailureOr<InliningPlan> plan = makePlan(useGraph, tables);
1240 signalPassFailure();
1245 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.