|
65 | 65 | /// any point in the pipeline. Until then, we could settle for a partially
|
66 | 66 | /// working AccessEnforcementSelection, or expand it somewhat to handle
|
67 | 67 | /// alloc_stack.
|
| 68 | +/// |
| 69 | +/// **Access marker merger** |
| 70 | +/// |
| 71 | +/// When a pair of non-overlapping accesses, where the first access dominates |
| 72 | +/// the second and there are no conflicts on the same storage in the paths |
| 73 | +/// between them, and they are part of the same sub-region |
| 74 | +/// be it the same block or the sampe loop, merge those accesses to create |
| 75 | +/// a new, larger, scope with a single begin_access for the accesses. |
68 | 76 | //===----------------------------------------------------------------------===//
|
69 | 77 |
|
70 | 78 | #define DEBUG_TYPE "access-enforcement-opts"
|
|
73 | 81 | #include "swift/SIL/MemAccessUtils.h"
|
74 | 82 | #include "swift/SIL/SILFunction.h"
|
75 | 83 | #include "swift/SILOptimizer/Analysis/AccessedStorageAnalysis.h"
|
| 84 | +#include "swift/SILOptimizer/Analysis/DominanceAnalysis.h" |
76 | 85 | #include "swift/SILOptimizer/Analysis/LoopRegionAnalysis.h"
|
77 | 86 | #include "swift/SILOptimizer/PassManager/Transforms.h"
|
78 | 87 | #include "swift/SILOptimizer/Utils/Local.h"
|
79 | 88 | #include "llvm/ADT/MapVector.h"
|
| 89 | +#include "llvm/ADT/SCCIterator.h" |
80 | 90 |
|
81 | 91 | using namespace swift;
|
82 | 92 |
|
@@ -871,6 +881,172 @@ removeLocalNonNestedAccess(const AccessConflictAndMergeAnalysis::Result &result,
|
871 | 881 | return changed;
|
872 | 882 | }
|
873 | 883 |
|
| 884 | +// TODO: support multi-end access cases |
| 885 | +static EndAccessInst *getSingleEndAccess(BeginAccessInst *inst) { |
| 886 | + EndAccessInst *end = nullptr; |
| 887 | + for (auto *currEnd : inst->getEndAccesses()) { |
| 888 | + if (end == nullptr) |
| 889 | + end = currEnd; |
| 890 | + else |
| 891 | + return nullptr; |
| 892 | + } |
| 893 | + return end; |
| 894 | +} |
| 895 | + |
| 896 | +struct SCCInfo { |
| 897 | + unsigned id; |
| 898 | + bool hasLoop; |
| 899 | +}; |
| 900 | + |
| 901 | +static void mergeEndAccesses(BeginAccessInst *parentIns, |
| 902 | + BeginAccessInst *childIns) { |
| 903 | + auto *endP = getSingleEndAccess(parentIns); |
| 904 | + if (!endP) |
| 905 | + llvm_unreachable("not supported"); |
| 906 | + auto *endC = getSingleEndAccess(childIns); |
| 907 | + if (!endC) |
| 908 | + llvm_unreachable("not supported"); |
| 909 | + |
| 910 | + endC->setOperand(parentIns); |
| 911 | + endP->eraseFromParent(); |
| 912 | +} |
| 913 | + |
| 914 | +static bool canMergeEnd(BeginAccessInst *parentIns, BeginAccessInst *childIns) { |
| 915 | + auto *endP = getSingleEndAccess(parentIns); |
| 916 | + if (!endP) |
| 917 | + return false; |
| 918 | + |
| 919 | + auto *endC = getSingleEndAccess(childIns); |
| 920 | + if (!endC) |
| 921 | + return false; |
| 922 | + |
| 923 | + return true; |
| 924 | +} |
| 925 | + |
| 926 | +// TODO: support other merge patterns |
| 927 | +static bool |
| 928 | +canMergeBegin(PostDominanceInfo *postDomTree, |
| 929 | + const llvm::DenseMap<SILBasicBlock *, SCCInfo> &blockToSCCMap, |
| 930 | + BeginAccessInst *parentIns, BeginAccessInst *childIns) { |
| 931 | + if (!postDomTree->properlyDominates(childIns, parentIns)) { |
| 932 | + return false; |
| 933 | + } |
| 934 | + auto parentSCCIt = blockToSCCMap.find(parentIns->getParent()); |
| 935 | + assert(parentSCCIt != blockToSCCMap.end() && "Expected block in SCC Map"); |
| 936 | + auto childSCCIt = blockToSCCMap.find(childIns->getParent()); |
| 937 | + assert(childSCCIt != blockToSCCMap.end() && "Expected block in SCC Map"); |
| 938 | + auto parentSCC = parentSCCIt->getSecond(); |
| 939 | + auto childSCC = childSCCIt->getSecond(); |
| 940 | + if (parentSCC.id == childSCC.id) { |
| 941 | + return true; |
| 942 | + } |
| 943 | + if (parentSCC.hasLoop) { |
| 944 | + return false; |
| 945 | + } |
| 946 | + if (childSCC.hasLoop) { |
| 947 | + return false; |
| 948 | + } |
| 949 | + return true; |
| 950 | +} |
| 951 | + |
| 952 | +static bool |
| 953 | +canMerge(PostDominanceInfo *postDomTree, |
| 954 | + const llvm::DenseMap<SILBasicBlock *, SCCInfo> &blockToSCCMap, |
| 955 | + BeginAccessInst *parentIns, BeginAccessInst *childIns) { |
| 956 | + if (!canMergeBegin(postDomTree, blockToSCCMap, parentIns, childIns)) |
| 957 | + return false; |
| 958 | + |
| 959 | + return canMergeEnd(parentIns, childIns); |
| 960 | +} |
| 961 | + |
| 962 | +/// Perform access merging. |
| 963 | +static bool mergeAccesses( |
| 964 | + SILFunction *F, PostDominanceInfo *postDomTree, |
| 965 | + const AccessConflictAndMergeAnalysis::MergeablePairs &mergePairs) { |
| 966 | + bool changed = false; |
| 967 | + |
| 968 | + // Compute a map from each block to its SCC - |
| 969 | + // For now we can't merge cross SCC boundary |
| 970 | + llvm::DenseMap<SILBasicBlock *, SCCInfo> blockToSCCMap; |
| 971 | + SCCInfo info; |
| 972 | + info.id = 0; |
| 973 | + for (auto sccIt = scc_begin(F); !sccIt.isAtEnd(); ++sccIt) { |
| 974 | + ++info.id; |
| 975 | + info.hasLoop = sccIt.hasLoop(); |
| 976 | + for (auto *bb : *sccIt) { |
| 977 | + blockToSCCMap.insert(std::make_pair(bb, info)); |
| 978 | + } |
| 979 | + } |
| 980 | + // make a temporary reverse copy to work on: |
| 981 | + // It is in reverse order just to make it easier to debug / follow |
| 982 | + AccessConflictAndMergeAnalysis::MergeablePairs workPairs; |
| 983 | + workPairs.append(mergePairs.rbegin(), mergePairs.rend()); |
| 984 | + |
| 985 | + // Assume the result contains two access pairs to be merged: |
| 986 | + // (begin_access %1, begin_access %2) |
| 987 | + // = merge end_access %1 with begin_access %2 |
| 988 | + // (begin_access %2, begin_access %3) |
| 989 | + // = merge end_access %2 with begin_access %3 |
| 990 | + // After merging the first pair, begin_access %2 is removed, |
| 991 | + // so the second pair in the result list points to a to-be-deleted |
| 992 | + // begin_access instruction. We store (begin_access %2 -> begin_access %1) |
| 993 | + // to re-map a merged begin_access to it's replaced instruction. |
| 994 | + llvm::DenseMap<BeginAccessInst *, BeginAccessInst *> oldToNewMap; |
| 995 | + |
| 996 | + while (!workPairs.empty()) { |
| 997 | + auto curr = workPairs.pop_back_val(); |
| 998 | + auto *parentIns = curr.first; |
| 999 | + auto *childIns = curr.second; |
| 1000 | + if (oldToNewMap.count(parentIns) != 0) { |
| 1001 | + parentIns = oldToNewMap[parentIns]; |
| 1002 | + } |
| 1003 | + assert(oldToNewMap.count(childIns) == 0 && |
| 1004 | + "Can't have same child instruction twice in map"); |
| 1005 | + |
| 1006 | + // The optimization might not currently support every mergeable pair |
| 1007 | + // If the current pattern is not supported - skip |
| 1008 | + if (!canMerge(postDomTree, blockToSCCMap, parentIns, childIns)) |
| 1009 | + continue; |
| 1010 | + |
| 1011 | + LLVM_DEBUG(llvm::dbgs() |
| 1012 | + << "Merging: " << *childIns << " into " << *parentIns << "\n"); |
| 1013 | + |
| 1014 | + // Change the type of access of parent: |
| 1015 | + // should be the worse between it and child |
| 1016 | + auto childAccess = childIns->getAccessKind(); |
| 1017 | + if (parentIns->getAccessKind() < childAccess) { |
| 1018 | + parentIns->setAccessKind(childAccess); |
| 1019 | + } |
| 1020 | + |
| 1021 | + // Change the no nested conflict of parent: |
| 1022 | + // should be the worst case scenario: we might merge to non-conflicting |
| 1023 | + // scopes to a conflicting one. f the new result does not conflict, |
| 1024 | + // a later on pass will remove the flag |
| 1025 | + parentIns->setNoNestedConflict(false); |
| 1026 | + |
| 1027 | + // remove end accesses and create new ones that cover bigger scope: |
| 1028 | + mergeEndAccesses(parentIns, childIns); |
| 1029 | + |
| 1030 | + // In case the child instruction is at the map, |
| 1031 | + // updated the oldToNewMap to reflect that we are getting rid of it: |
| 1032 | + oldToNewMap.insert(std::make_pair(childIns, parentIns)); |
| 1033 | + |
| 1034 | + // Modify the users of child instruction to use the parent: |
| 1035 | + childIns->replaceAllUsesWith(parentIns); |
| 1036 | + |
| 1037 | + changed = true; |
| 1038 | + } |
| 1039 | + |
| 1040 | + // Delete all old instructions from parent scopes: |
| 1041 | + while (!oldToNewMap.empty()) { |
| 1042 | + auto curr = oldToNewMap.begin(); |
| 1043 | + auto *oldIns = curr->getFirst(); |
| 1044 | + oldToNewMap.erase(oldIns); |
| 1045 | + oldIns->eraseFromParent(); |
| 1046 | + } |
| 1047 | + return changed; |
| 1048 | +} |
| 1049 | + |
874 | 1050 | namespace {
|
875 | 1051 | struct AccessEnforcementOpts : public SILFunctionTransform {
|
876 | 1052 | void run() override {
|
@@ -905,6 +1081,14 @@ struct AccessEnforcementOpts : public SILFunctionTransform {
|
905 | 1081 | const FunctionAccessedStorage &functionAccess = ASA->getEffects(F);
|
906 | 1082 | if (removeLocalNonNestedAccess(result, functionAccess))
|
907 | 1083 | invalidateAnalysis(SILAnalysis::InvalidationKind::Instructions);
|
| 1084 | + |
| 1085 | + // Perform the access merging |
| 1086 | + // The inital version of the optimization requires a postDomTree |
| 1087 | + PostDominanceAnalysis *postDomAnalysis = |
| 1088 | + getAnalysis<PostDominanceAnalysis>(); |
| 1089 | + PostDominanceInfo *postDomTree = postDomAnalysis->get(F); |
| 1090 | + if (mergeAccesses(F, postDomTree, result.mergePairs)) |
| 1091 | + invalidateAnalysis(SILAnalysis::InvalidationKind::Instructions); |
908 | 1092 | }
|
909 | 1093 | };
|
910 | 1094 | } // namespace
|
|
0 commit comments