diff --git a/llvm/include/llvm/Transforms/Scalar/GVN.h b/llvm/include/llvm/Transforms/Scalar/GVN.h --- a/llvm/include/llvm/Transforms/Scalar/GVN.h +++ b/llvm/include/llvm/Transforms/Scalar/GVN.h @@ -73,6 +73,7 @@ Optional AllowPRE = None; Optional AllowLoadPRE = None; Optional AllowLoadInLoopPRE = None; + Optional AllowLoadPRESplitBackedge = None; Optional AllowMemDep = None; GVNOptions() = default; @@ -94,6 +95,12 @@ return *this; } + /// Enables or disables PRE of loads in GVN. + GVNOptions &setLoadPRESplitBackedge(bool LoadPRESplitBackedge) { + AllowLoadPRESplitBackedge = LoadPRESplitBackedge; + return *this; + } + /// Enables or disables use of MemDepAnalysis. GVNOptions &setMemDep(bool MemDep) { AllowMemDep = MemDep; @@ -130,6 +137,7 @@ bool isPREEnabled() const; bool isLoadPREEnabled() const; bool isLoadInLoopPREEnabled() const; + bool isLoadPRESplitBackedgeEnabled() const; bool isMemDepEnabled() const; /// This class holds the mapping between values and value numbers. It is used diff --git a/llvm/lib/Passes/PassBuilder.cpp b/llvm/lib/Passes/PassBuilder.cpp --- a/llvm/lib/Passes/PassBuilder.cpp +++ b/llvm/lib/Passes/PassBuilder.cpp @@ -1926,6 +1926,8 @@ Result.setPRE(Enable); } else if (ParamName == "load-pre") { Result.setLoadPRE(Enable); + } else if (ParamName == "split-backedge-load-pre") { + Result.setLoadPRESplitBackedge(Enable); } else if (ParamName == "memdep") { Result.setMemDep(Enable); } else { diff --git a/llvm/lib/Transforms/Scalar/GVN.cpp b/llvm/lib/Transforms/Scalar/GVN.cpp --- a/llvm/lib/Transforms/Scalar/GVN.cpp +++ b/llvm/lib/Transforms/Scalar/GVN.cpp @@ -110,6 +110,9 @@ static cl::opt GVNEnableLoadPRE("enable-load-pre", cl::init(true)); static cl::opt GVNEnableLoadInLoopPRE("enable-load-in-loop-pre", cl::init(true)); +static cl::opt +GVNEnableSplitBackedgeInLoadPRE("enable-split-backedge-in-load-pre", + cl::init(true)); static cl::opt GVNEnableMemDep("enable-gvn-memdep", cl::init(true)); static cl::opt MaxNumDeps( @@ -639,6 +642,11 @@ return Options.AllowLoadInLoopPRE.getValueOr(GVNEnableLoadInLoopPRE); } +bool GVN::isLoadPRESplitBackedgeEnabled() const { + return Options.AllowLoadPRESplitBackedge.getValueOr( + GVNEnableSplitBackedgeInLoadPRE); +} + bool GVN::isMemDepEnabled() const { return Options.AllowMemDep.getValueOr(GVNEnableMemDep); } @@ -1222,6 +1230,16 @@ return false; } + // Do not split backedge as it will break the canonical loop form. + if (!isLoadPRESplitBackedgeEnabled()) + if (DT->dominates(LoadBB, Pred)) { + LLVM_DEBUG( + dbgs() + << "COULD NOT PRE LOAD BECAUSE OF A BACKEDGE CRITICAL EDGE '" + << Pred->getName() << "': " << *LI << '\n'); + return false; + } + CriticalEdgePred.push_back(Pred); } else { // Only add the predecessors that will not be split for now. diff --git a/llvm/test/Transforms/GVN/PRE/load-pre-split-backedge.ll b/llvm/test/Transforms/GVN/PRE/load-pre-split-backedge.ll new file mode 100644 --- /dev/null +++ b/llvm/test/Transforms/GVN/PRE/load-pre-split-backedge.ll @@ -0,0 +1,57 @@ +; NOTE: Assertions have been autogenerated by utils/update_test_checks.py +; RUN: opt -S -basic-aa -gvn -enable-split-backedge-in-load-pre=true < %s | FileCheck %s --check-prefix=ON +; RUN: opt -S -basic-aa -gvn -enable-split-backedge-in-load-pre=false < %s | FileCheck %s --check-prefix=OFF + +target datalayout = "e-p:32:32:32-i1:8:8-i8:8:8-i16:16:16-i32:32:32-i64:32:64-f32:32:32-f64:32:64-v64:64:64-v128:128:128-a0:0:64-f80:128:128-n8:16:32" + +define i32 @test(i1 %b, i1 %c, i32* noalias %p, i32* noalias %q) { +; ON-LABEL: @test( +; ON-NEXT: entry: +; ON-NEXT: [[Y1:%.*]] = load i32, i32* [[P:%.*]], align 4 +; ON-NEXT: call void @use(i32 [[Y1]]) +; ON-NEXT: br label [[HEADER:%.*]] +; ON: header: +; ON-NEXT: [[Y:%.*]] = phi i32 [ [[Y_PRE:%.*]], [[SKIP_HEADER_CRIT_EDGE:%.*]] ], [ [[Y]], [[HEADER]] ], [ [[Y1]], [[ENTRY:%.*]] ] +; ON-NEXT: call void @use(i32 [[Y]]) +; ON-NEXT: br i1 [[B:%.*]], label [[SKIP:%.*]], label [[HEADER]] +; ON: skip: +; ON-NEXT: call void @clobber(i32* [[P]], i32* [[Q:%.*]]) +; ON-NEXT: br i1 [[C:%.*]], label [[SKIP_HEADER_CRIT_EDGE]], label [[EXIT:%.*]] +; ON: skip.header_crit_edge: +; ON-NEXT: [[Y_PRE]] = load i32, i32* [[P]], align 4 +; ON-NEXT: br label [[HEADER]] +; ON: exit: +; ON-NEXT: ret i32 [[Y]] +; +; OFF-LABEL: @test( +; OFF-NEXT: entry: +; OFF-NEXT: [[Y1:%.*]] = load i32, i32* [[P:%.*]], align 4 +; OFF-NEXT: call void @use(i32 [[Y1]]) +; OFF-NEXT: br label [[HEADER:%.*]] +; OFF: header: +; OFF-NEXT: [[Y:%.*]] = load i32, i32* [[P]], align 4 +; OFF-NEXT: call void @use(i32 [[Y]]) +; OFF-NEXT: br i1 [[B:%.*]], label [[SKIP:%.*]], label [[HEADER]] +; OFF: skip: +; OFF-NEXT: call void @clobber(i32* [[P]], i32* [[Q:%.*]]) +; OFF-NEXT: br i1 [[C:%.*]], label [[HEADER]], label [[EXIT:%.*]] +; OFF: exit: +; OFF-NEXT: ret i32 [[Y]] +; +entry: + %y1 = load i32, i32* %p + call void @use(i32 %y1) + br label %header +header: + %y = load i32, i32* %p + call void @use(i32 %y) + br i1 %b, label %skip, label %header +skip: + call void @clobber(i32* %p, i32* %q) + br i1 %c, label %header, label %exit +exit: + ret i32 %y +} + +declare void @use(i32) readonly +declare void @clobber(i32* %p, i32* %q)