From 830a8c2e98caf608f974e6afd11b0161ad270cb3 Mon Sep 17 00:00:00 2001 From: Hans Wennborg <hans@hanshq.net> Date: Sat, 7 Feb 2015 23:09:08 +0000 Subject: [PATCH 1/6] Merging r228507: ------------------------------------------------------------------------ r228507 | joerg | 2015-02-07 13:24:06 -0800 (Sat, 07 Feb 2015) | 4 lines Avoid integer overflows around realloc calls resulting in potential heap. Problem identified by Guido Vranken. Changes differ from original OpenBSD sources by not depending on non-portable reallocarray. ------------------------------------------------------------------------ git-svn-id: https://llvm.org/svn/llvm-project/llvm/branches/release_36@228511 91177308-0d34-0410-b5e6-96231b3b80d8 --- lib/Support/regcomp.c | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/lib/Support/regcomp.c b/lib/Support/regcomp.c index 0b5b765f89e1..b79692966473 100644 --- a/lib/Support/regcomp.c +++ b/lib/Support/regcomp.c @@ -49,6 +49,14 @@ #include "regcclass.h" #include "regcname.h" +#include "llvm/Config/config.h" +#if HAVE_STDINT_H +#include <stdint.h> +#else +/* Pessimistically bound memory use */ +#define SIZE_MAX UINT_MAX +#endif + /* * parse structure, passed up and down to avoid global variables and * other clumsinesses @@ -1069,6 +1077,8 @@ allocset(struct parse *p) p->ncsalloc += CHAR_BIT; nc = p->ncsalloc; + if (nc > SIZE_MAX / sizeof(cset)) + goto nomem; assert(nc % CHAR_BIT == 0); nbytes = nc / CHAR_BIT * css; @@ -1412,6 +1422,11 @@ enlarge(struct parse *p, sopno size) if (p->ssize >= size) return; + if ((unsigned long)size > SIZE_MAX / sizeof(sop)) { + SETERROR(REG_ESPACE); + return; + } + sp = (sop *)realloc(p->strip, size*sizeof(sop)); if (sp == NULL) { SETERROR(REG_ESPACE); @@ -1428,6 +1443,12 @@ static void stripsnug(struct parse *p, struct re_guts *g) { g->nstates = p->slen; + if ((unsigned long)p->slen > SIZE_MAX / sizeof(sop)) { + g->strip = p->strip; + SETERROR(REG_ESPACE); + return; + } + g->strip = (sop *)realloc((char *)p->strip, p->slen * sizeof(sop)); if (g->strip == NULL) { SETERROR(REG_ESPACE); From a370ce2421aa739a448a4a0edd456e804ee23257 Mon Sep 17 00:00:00 2001 From: Hans Wennborg <hans@hanshq.net> Date: Mon, 9 Feb 2015 02:38:04 +0000 Subject: [PATCH 2/6] Merging r228518: ------------------------------------------------------------------------ r228518 | tnorthover | 2015-02-07 16:50:47 -0800 (Sat, 07 Feb 2015) | 15 lines ARM & AArch64: teach LowerVSETCC that output type size may differ from input. While various DAG combines try to guarantee that a vector SETCC operation will have the same output size as input, there's nothing intrinsic to either creation or LegalizeTypes that actually guarantees it, so the function needs to be ready to handle a mismatch. Fortunately this is easy enough, just extend or truncate the naturally compared result. I couldn't reproduce the failure in other backends that I know have SIMD, so it's probably only an issue for these two due to shared heritage. Should fix PR21645. ------------------------------------------------------------------------ git-svn-id: https://llvm.org/svn/llvm-project/llvm/branches/release_36@228560 91177308-0d34-0410-b5e6-96231b3b80d8 --- lib/Target/AArch64/AArch64ISelLowering.cpp | 16 ++++++++---- lib/Target/ARM/ARMISelLowering.cpp | 29 ++++++++++++--------- test/CodeGen/AArch64/setcc-type-mismatch.ll | 11 ++++++++ test/CodeGen/ARM/setcc-type-mismatch.ll | 11 ++++++++ 4 files changed, 49 insertions(+), 18 deletions(-) create mode 100644 test/CodeGen/AArch64/setcc-type-mismatch.ll create mode 100644 test/CodeGen/ARM/setcc-type-mismatch.ll diff --git a/lib/Target/AArch64/AArch64ISelLowering.cpp b/lib/Target/AArch64/AArch64ISelLowering.cpp index 19f51ce9450c..399b5eeaf5f5 100644 --- a/lib/Target/AArch64/AArch64ISelLowering.cpp +++ b/lib/Target/AArch64/AArch64ISelLowering.cpp @@ -6287,6 +6287,8 @@ static SDValue EmitVectorComparison(SDValue LHS, SDValue RHS, AArch64CC::CondCode CC, bool NoNans, EVT VT, SDLoc dl, SelectionDAG &DAG) { EVT SrcVT = LHS.getValueType(); + assert(VT.getSizeInBits() == SrcVT.getSizeInBits() && + "function only supposed to emit natural comparisons"); BuildVectorSDNode *BVN = dyn_cast<BuildVectorSDNode>(RHS.getNode()); APInt CnstBits(VT.getSizeInBits(), 0); @@ -6381,13 +6383,15 @@ SDValue AArch64TargetLowering::LowerVSETCC(SDValue Op, ISD::CondCode CC = cast<CondCodeSDNode>(Op.getOperand(2))->get(); SDValue LHS = Op.getOperand(0); SDValue RHS = Op.getOperand(1); + EVT CmpVT = LHS.getValueType().changeVectorElementTypeToInteger(); SDLoc dl(Op); if (LHS.getValueType().getVectorElementType().isInteger()) { assert(LHS.getValueType() == RHS.getValueType()); AArch64CC::CondCode AArch64CC = changeIntCCToAArch64CC(CC); - return EmitVectorComparison(LHS, RHS, AArch64CC, false, Op.getValueType(), - dl, DAG); + SDValue Cmp = + EmitVectorComparison(LHS, RHS, AArch64CC, false, CmpVT, dl, DAG); + return DAG.getSExtOrTrunc(Cmp, dl, Op.getValueType()); } assert(LHS.getValueType().getVectorElementType() == MVT::f32 || @@ -6401,19 +6405,21 @@ SDValue AArch64TargetLowering::LowerVSETCC(SDValue Op, bool NoNaNs = getTargetMachine().Options.NoNaNsFPMath; SDValue Cmp = - EmitVectorComparison(LHS, RHS, CC1, NoNaNs, Op.getValueType(), dl, DAG); + EmitVectorComparison(LHS, RHS, CC1, NoNaNs, CmpVT, dl, DAG); if (!Cmp.getNode()) return SDValue(); if (CC2 != AArch64CC::AL) { SDValue Cmp2 = - EmitVectorComparison(LHS, RHS, CC2, NoNaNs, Op.getValueType(), dl, DAG); + EmitVectorComparison(LHS, RHS, CC2, NoNaNs, CmpVT, dl, DAG); if (!Cmp2.getNode()) return SDValue(); - Cmp = DAG.getNode(ISD::OR, dl, Cmp.getValueType(), Cmp, Cmp2); + Cmp = DAG.getNode(ISD::OR, dl, CmpVT, Cmp, Cmp2); } + Cmp = DAG.getSExtOrTrunc(Cmp, dl, Op.getValueType()); + if (ShouldInvert) return Cmp = DAG.getNOT(dl, Cmp, Cmp.getValueType()); diff --git a/lib/Target/ARM/ARMISelLowering.cpp b/lib/Target/ARM/ARMISelLowering.cpp index 90a3d3438f8c..a1de5efb4507 100644 --- a/lib/Target/ARM/ARMISelLowering.cpp +++ b/lib/Target/ARM/ARMISelLowering.cpp @@ -4487,6 +4487,7 @@ static SDValue LowerVSETCC(SDValue Op, SelectionDAG &DAG) { SDValue Op0 = Op.getOperand(0); SDValue Op1 = Op.getOperand(1); SDValue CC = Op.getOperand(2); + EVT CmpVT = Op0.getValueType().changeVectorElementTypeToInteger(); EVT VT = Op.getValueType(); ISD::CondCode SetCCOpcode = cast<CondCodeSDNode>(CC)->get(); SDLoc dl(Op); @@ -4516,8 +4517,8 @@ static SDValue LowerVSETCC(SDValue Op, SelectionDAG &DAG) { TmpOp0 = Op0; TmpOp1 = Op1; Opc = ISD::OR; - Op0 = DAG.getNode(ARMISD::VCGT, dl, VT, TmpOp1, TmpOp0); - Op1 = DAG.getNode(ARMISD::VCGT, dl, VT, TmpOp0, TmpOp1); + Op0 = DAG.getNode(ARMISD::VCGT, dl, CmpVT, TmpOp1, TmpOp0); + Op1 = DAG.getNode(ARMISD::VCGT, dl, CmpVT, TmpOp0, TmpOp1); break; case ISD::SETUO: Invert = true; // Fallthrough case ISD::SETO: @@ -4525,8 +4526,8 @@ static SDValue LowerVSETCC(SDValue Op, SelectionDAG &DAG) { TmpOp0 = Op0; TmpOp1 = Op1; Opc = ISD::OR; - Op0 = DAG.getNode(ARMISD::VCGT, dl, VT, TmpOp1, TmpOp0); - Op1 = DAG.getNode(ARMISD::VCGE, dl, VT, TmpOp0, TmpOp1); + Op0 = DAG.getNode(ARMISD::VCGT, dl, CmpVT, TmpOp1, TmpOp0); + Op1 = DAG.getNode(ARMISD::VCGE, dl, CmpVT, TmpOp0, TmpOp1); break; } } else { @@ -4560,8 +4561,8 @@ static SDValue LowerVSETCC(SDValue Op, SelectionDAG &DAG) { if (AndOp.getNode() && AndOp.getOpcode() == ISD::AND) { Opc = ARMISD::VTST; - Op0 = DAG.getNode(ISD::BITCAST, dl, VT, AndOp.getOperand(0)); - Op1 = DAG.getNode(ISD::BITCAST, dl, VT, AndOp.getOperand(1)); + Op0 = DAG.getNode(ISD::BITCAST, dl, CmpVT, AndOp.getOperand(0)); + Op1 = DAG.getNode(ISD::BITCAST, dl, CmpVT, AndOp.getOperand(1)); Invert = !Invert; } } @@ -4587,22 +4588,24 @@ static SDValue LowerVSETCC(SDValue Op, SelectionDAG &DAG) { if (SingleOp.getNode()) { switch (Opc) { case ARMISD::VCEQ: - Result = DAG.getNode(ARMISD::VCEQZ, dl, VT, SingleOp); break; + Result = DAG.getNode(ARMISD::VCEQZ, dl, CmpVT, SingleOp); break; case ARMISD::VCGE: - Result = DAG.getNode(ARMISD::VCGEZ, dl, VT, SingleOp); break; + Result = DAG.getNode(ARMISD::VCGEZ, dl, CmpVT, SingleOp); break; case ARMISD::VCLEZ: - Result = DAG.getNode(ARMISD::VCLEZ, dl, VT, SingleOp); break; + Result = DAG.getNode(ARMISD::VCLEZ, dl, CmpVT, SingleOp); break; case ARMISD::VCGT: - Result = DAG.getNode(ARMISD::VCGTZ, dl, VT, SingleOp); break; + Result = DAG.getNode(ARMISD::VCGTZ, dl, CmpVT, SingleOp); break; case ARMISD::VCLTZ: - Result = DAG.getNode(ARMISD::VCLTZ, dl, VT, SingleOp); break; + Result = DAG.getNode(ARMISD::VCLTZ, dl, CmpVT, SingleOp); break; default: - Result = DAG.getNode(Opc, dl, VT, Op0, Op1); + Result = DAG.getNode(Opc, dl, CmpVT, Op0, Op1); } } else { - Result = DAG.getNode(Opc, dl, VT, Op0, Op1); + Result = DAG.getNode(Opc, dl, CmpVT, Op0, Op1); } + Result = DAG.getSExtOrTrunc(Result, dl, VT); + if (Invert) Result = DAG.getNOT(dl, Result, VT); diff --git a/test/CodeGen/AArch64/setcc-type-mismatch.ll b/test/CodeGen/AArch64/setcc-type-mismatch.ll new file mode 100644 index 000000000000..86817fa4fa40 --- /dev/null +++ b/test/CodeGen/AArch64/setcc-type-mismatch.ll @@ -0,0 +1,11 @@ +; RUN: llc -mtriple=aarch64-linux-gnu %s -o - | FileCheck %s + +define void @test_mismatched_setcc(<4 x i22> %l, <4 x i22> %r, <4 x i1>* %addr) { +; CHECK-LABEL: test_mismatched_setcc: +; CHECK: cmeq [[CMP128:v[0-9]+]].4s, {{v[0-9]+}}.4s, {{v[0-9]+}}.4s +; CHECK: xtn {{v[0-9]+}}.4h, [[CMP128]].4s + + %tst = icmp eq <4 x i22> %l, %r + store <4 x i1> %tst, <4 x i1>* %addr + ret void +} diff --git a/test/CodeGen/ARM/setcc-type-mismatch.ll b/test/CodeGen/ARM/setcc-type-mismatch.ll new file mode 100644 index 000000000000..2cfdba12db54 --- /dev/null +++ b/test/CodeGen/ARM/setcc-type-mismatch.ll @@ -0,0 +1,11 @@ +; RUN: llc -mtriple=armv7-linux-gnueabihf %s -o - | FileCheck %s + +define void @test_mismatched_setcc(<4 x i22> %l, <4 x i22> %r, <4 x i1>* %addr) { +; CHECK-LABEL: test_mismatched_setcc: +; CHECK: vceq.i32 [[CMP128:q[0-9]+]], {{q[0-9]+}}, {{q[0-9]+}} +; CHECK: vmovn.i32 {{d[0-9]+}}, [[CMP128]] + + %tst = icmp eq <4 x i22> %l, %r + store <4 x i1> %tst, <4 x i1>* %addr + ret void +} From 412f882ee0609da404126a768ece6e58d276898a Mon Sep 17 00:00:00 2001 From: Hans Wennborg <hans@hanshq.net> Date: Mon, 9 Feb 2015 03:35:35 +0000 Subject: [PATCH 3/6] Merging r228525: ------------------------------------------------------------------------ r228525 | bsteinbr | 2015-02-08 09:07:14 -0800 (Sun, 08 Feb 2015) | 14 lines Correctly combine alias.scope metadata by a union instead of intersecting Summary: The alias.scope metadata represents sets of things an instruction might alias with. When generically combining the metadata from two instructions the result must be the union of the original sets, because the new instruction might alias with anything any of the original instructions aliased with. Reviewers: hfinkel Subscribers: llvm-commits Differential Revision: http://reviews.llvm.org/D7490 ------------------------------------------------------------------------ git-svn-id: https://llvm.org/svn/llvm-project/llvm/branches/release_36@228561 91177308-0d34-0410-b5e6-96231b3b80d8 --- include/llvm/IR/Metadata.h | 1 + lib/Analysis/TypeBasedAliasAnalysis.cpp | 4 ++-- lib/IR/Metadata.cpp | 22 +++++++++++++++++ lib/Transforms/Utils/Local.cpp | 2 ++ lib/Transforms/Vectorize/SLPVectorizer.cpp | 2 ++ .../Util/combine-alias-scope-metadata.ll | 24 +++++++++++++++++++ 6 files changed, 53 insertions(+), 2 deletions(-) create mode 100644 test/Transforms/Util/combine-alias-scope-metadata.ll diff --git a/include/llvm/IR/Metadata.h b/include/llvm/IR/Metadata.h index 3bf6d38d311d..27a5d6f80041 100644 --- a/include/llvm/IR/Metadata.h +++ b/include/llvm/IR/Metadata.h @@ -693,6 +693,7 @@ class MDNode : public Metadata { static AAMDNodes getMostGenericAA(const AAMDNodes &A, const AAMDNodes &B); static MDNode *getMostGenericFPMath(MDNode *A, MDNode *B); static MDNode *getMostGenericRange(MDNode *A, MDNode *B); + static MDNode *getMostGenericAliasScope(MDNode *A, MDNode *B); }; /// \brief Uniquable metadata node. diff --git a/lib/Analysis/TypeBasedAliasAnalysis.cpp b/lib/Analysis/TypeBasedAliasAnalysis.cpp index 085ce920139b..ff8955870cb5 100644 --- a/lib/Analysis/TypeBasedAliasAnalysis.cpp +++ b/lib/Analysis/TypeBasedAliasAnalysis.cpp @@ -623,8 +623,8 @@ void Instruction::getAAMetadata(AAMDNodes &N, bool Merge) const { N.TBAA = getMetadata(LLVMContext::MD_tbaa); if (Merge) - N.Scope = - MDNode::intersect(N.Scope, getMetadata(LLVMContext::MD_alias_scope)); + N.Scope = MDNode::getMostGenericAliasScope( + N.Scope, getMetadata(LLVMContext::MD_alias_scope)); else N.Scope = getMetadata(LLVMContext::MD_alias_scope); diff --git a/lib/IR/Metadata.cpp b/lib/IR/Metadata.cpp index 2c6b332dc8ee..63e5730f954e 100644 --- a/lib/IR/Metadata.cpp +++ b/lib/IR/Metadata.cpp @@ -826,6 +826,28 @@ MDNode *MDNode::intersect(MDNode *A, MDNode *B) { return getOrSelfReference(A->getContext(), MDs); } +MDNode *MDNode::getMostGenericAliasScope(MDNode *A, MDNode *B) { + if (!A || !B) + return nullptr; + + SmallVector<Metadata *, 4> MDs(B->op_begin(), B->op_end()); + for (unsigned i = 0, ie = A->getNumOperands(); i != ie; ++i) { + Metadata *MD = A->getOperand(i); + bool insert = true; + for (unsigned j = 0, je = B->getNumOperands(); j != je; ++j) + if (MD == B->getOperand(j)) { + insert = false; + break; + } + if (insert) + MDs.push_back(MD); + } + + // FIXME: This preserves long-standing behaviour, but is it really the right + // behaviour? Or was that an unintended side-effect of node uniquing? + return getOrSelfReference(A->getContext(), MDs); +} + MDNode *MDNode::getMostGenericFPMath(MDNode *A, MDNode *B) { if (!A || !B) return nullptr; diff --git a/lib/Transforms/Utils/Local.cpp b/lib/Transforms/Utils/Local.cpp index 08a4b3f3b737..2a84d7e0c484 100644 --- a/lib/Transforms/Utils/Local.cpp +++ b/lib/Transforms/Utils/Local.cpp @@ -1328,6 +1328,8 @@ void llvm::combineMetadata(Instruction *K, const Instruction *J, ArrayRef<unsign K->setMetadata(Kind, MDNode::getMostGenericTBAA(JMD, KMD)); break; case LLVMContext::MD_alias_scope: + K->setMetadata(Kind, MDNode::getMostGenericAliasScope(JMD, KMD)); + break; case LLVMContext::MD_noalias: K->setMetadata(Kind, MDNode::intersect(JMD, KMD)); break; diff --git a/lib/Transforms/Vectorize/SLPVectorizer.cpp b/lib/Transforms/Vectorize/SLPVectorizer.cpp index 4834782ecc14..2213031ecdb9 100644 --- a/lib/Transforms/Vectorize/SLPVectorizer.cpp +++ b/lib/Transforms/Vectorize/SLPVectorizer.cpp @@ -208,6 +208,8 @@ static Instruction *propagateMetadata(Instruction *I, ArrayRef<Value *> VL) { MD = MDNode::getMostGenericTBAA(MD, IMD); break; case LLVMContext::MD_alias_scope: + MD = MDNode::getMostGenericAliasScope(MD, IMD); + break; case LLVMContext::MD_noalias: MD = MDNode::intersect(MD, IMD); break; diff --git a/test/Transforms/Util/combine-alias-scope-metadata.ll b/test/Transforms/Util/combine-alias-scope-metadata.ll new file mode 100644 index 000000000000..fd0a3d5c5b92 --- /dev/null +++ b/test/Transforms/Util/combine-alias-scope-metadata.ll @@ -0,0 +1,24 @@ +; RUN: opt < %s -S -basicaa -memcpyopt | FileCheck %s +target datalayout = "e-m:e-i64:64-f80:128-n8:16:32:64-S128" + +define void @test(i8* noalias dereferenceable(1) %in, i8* noalias dereferenceable(1) %out) { + %tmp = alloca i8 + %tmp2 = alloca i8 +; CHECK: call void @llvm.memcpy.p0i8.p0i8.i64(i8* %out, i8* %in, i64 1, i32 8, i1 false) + call void @llvm.memcpy.p0i8.p0i8.i64(i8* %tmp, i8* %in, i64 1, i32 8, i1 false), !alias.scope !4 + call void @llvm.memcpy.p0i8.p0i8.i64(i8* %tmp2, i8* %tmp, i64 1, i32 8, i1 false), !alias.scope !5 + + call void @llvm.memcpy.p0i8.p0i8.i64(i8* %out, i8* %tmp2, i64 1, i32 8, i1 false), !noalias !6 + + ret void +} + +declare void @llvm.memcpy.p0i8.p0i8.i64(i8*, i8*, i64, i32, i1) + +!0 = !{!0} +!1 = distinct !{!1, !0, !"in"} +!2 = distinct !{!2, !0, !"tmp"} +!3 = distinct !{!3, !0, !"tmp2"} +!4 = distinct !{!1, !2} +!5 = distinct !{!2, !3} +!6 = distinct !{!1, !2} From b4e7a3159a08fb503901b5024b4ac5a2d03ec992 Mon Sep 17 00:00:00 2001 From: Cameron Zwarich <zwarich@mozilla.com> Date: Wed, 25 Jun 2014 20:33:49 -0700 Subject: [PATCH 4/6] Add a NullCheckElimination pass This pass is not Rust-specific, in that all of its transformations are intended to be correct for arbitrary LLVM IR, but it targets idioms found in IR generated by `rustc`, e.g. heavy use of `inbounds` GEPs. --- include/llvm/InitializePasses.h | 3 + include/llvm/LinkAllPasses.h | 3 + include/llvm/Transforms/Scalar.h | 7 + lib/Transforms/Scalar/CMakeLists.txt | 1 + .../Scalar/NullCheckElimination.cpp | 273 ++++++++++++++++++ lib/Transforms/Scalar/Scalar.cpp | 1 + test/Transforms/NullCheckElimination/basic.ll | 165 +++++++++++ 7 files changed, 453 insertions(+) create mode 100644 lib/Transforms/Scalar/NullCheckElimination.cpp create mode 100644 test/Transforms/NullCheckElimination/basic.ll diff --git a/include/llvm/InitializePasses.h b/include/llvm/InitializePasses.h index 30280033ee20..b9ea8489aeb2 100644 --- a/include/llvm/InitializePasses.h +++ b/include/llvm/InitializePasses.h @@ -287,6 +287,9 @@ void initializeStackMapLivenessPass(PassRegistry&); void initializeMachineCombinerPass(PassRegistry &); void initializeLoadCombinePass(PassRegistry&); void initializeRewriteSymbolsPass(PassRegistry&); + +// Specific to the rust-lang llvm branch: +void initializeNullCheckEliminationPass(PassRegistry&); } #endif diff --git a/include/llvm/LinkAllPasses.h b/include/llvm/LinkAllPasses.h index 2e8feab6d29d..1764652e37e7 100644 --- a/include/llvm/LinkAllPasses.h +++ b/include/llvm/LinkAllPasses.h @@ -167,6 +167,9 @@ namespace { (void) llvm::createSeparateConstOffsetFromGEPPass(); (void) llvm::createRewriteSymbolsPass(); + // Specific to the rust-lang llvm branch: + (void) llvm::createNullCheckEliminationPass(); + (void)new llvm::IntervalPartition(); (void)new llvm::ScalarEvolution(); ((llvm::Function*)nullptr)->viewCFGOnly(); diff --git a/include/llvm/Transforms/Scalar.h b/include/llvm/Transforms/Scalar.h index 5dcd89948759..12fd59b57919 100644 --- a/include/llvm/Transforms/Scalar.h +++ b/include/llvm/Transforms/Scalar.h @@ -405,6 +405,13 @@ createSeparateConstOffsetFromGEPPass(const TargetMachine *TM = nullptr, // BasicBlockPass *createLoadCombinePass(); +// Specific to the rust-lang llvm branch: +//===----------------------------------------------------------------------===// +// +// NullCheckElimination - Eliminate null checks. +// +FunctionPass *createNullCheckEliminationPass(); + } // End llvm namespace #endif diff --git a/lib/Transforms/Scalar/CMakeLists.txt b/lib/Transforms/Scalar/CMakeLists.txt index b3ee11ed67cd..4218a5bbbbf8 100644 --- a/lib/Transforms/Scalar/CMakeLists.txt +++ b/lib/Transforms/Scalar/CMakeLists.txt @@ -24,6 +24,7 @@ add_llvm_library(LLVMScalarOpts LowerAtomic.cpp MemCpyOptimizer.cpp MergedLoadStoreMotion.cpp + NullCheckElimination.cpp PartiallyInlineLibCalls.cpp Reassociate.cpp Reg2Mem.cpp diff --git a/lib/Transforms/Scalar/NullCheckElimination.cpp b/lib/Transforms/Scalar/NullCheckElimination.cpp new file mode 100644 index 000000000000..4f04bd65c204 --- /dev/null +++ b/lib/Transforms/Scalar/NullCheckElimination.cpp @@ -0,0 +1,273 @@ +//===-- NullCheckElimination.cpp - Null Check Elimination Pass ------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "llvm/Transforms/Scalar.h" +#include "llvm/ADT/DenseSet.h" +#include "llvm/ADT/SmallPtrSet.h" +#include "llvm/ADT/Statistic.h" +#include "llvm/IR/Constants.h" +#include "llvm/IR/Function.h" +#include "llvm/IR/Instructions.h" +#include "llvm/Pass.h" +using namespace llvm; + +#define DEBUG_TYPE "null-check-elimination" + +namespace { + struct NullCheckElimination : public FunctionPass { + static char ID; + NullCheckElimination() : FunctionPass(ID) { + initializeNullCheckEliminationPass(*PassRegistry::getPassRegistry()); + } + bool runOnFunction(Function &F) override; + + void getAnalysisUsage(AnalysisUsage &AU) const override { + AU.setPreservesCFG(); + } + + private: + static const unsigned kPhiLimit = 16; + typedef SmallPtrSet<PHINode*, kPhiLimit> SmallPhiSet; + enum NullCheckResult { + NotNullCheck, + NullCheckEq, + NullCheckNe, + }; + + bool isNonNullOrPoisonPhi(SmallPhiSet *VisitedPhis, PHINode*); + + NullCheckResult isCmpNullCheck(ICmpInst*); + std::pair<Use*, NullCheckResult> findNullCheck(Use*); + + bool blockContainsLoadDerivedFrom(BasicBlock*, Value*); + + DenseSet<Value*> NonNullOrPoisonValues; + }; +} + +char NullCheckElimination::ID = 0; +INITIALIZE_PASS_BEGIN(NullCheckElimination, + "null-check-elimination", + "Null Check Elimination", + false, false) +INITIALIZE_PASS_END(NullCheckElimination, + "null-check-elimination", + "Null Check Elimination", + false, false) + +FunctionPass *llvm::createNullCheckEliminationPass() { + return new NullCheckElimination(); +} + +bool NullCheckElimination::runOnFunction(Function &F) { + if (skipOptnoneFunction(F)) + return false; + + bool Changed = false; + + // Collect argumetns with the `nonnull` attribute. + for (auto &Arg : F.args()) { + if (Arg.hasNonNullAttr()) + NonNullOrPoisonValues.insert(&Arg); + } + + // Collect instructions that definitely produce nonnull-or-poison values. + // At the moment, this is restricted to inbounds GEPs. It would be slightly + // more difficult to include uses of values dominated by a null check, since + // then we would have to consider uses instead of mere values. + for (auto &BB : F) { + for (auto &I : BB) { + if (auto *GEP = dyn_cast<GetElementPtrInst>(&I)) { + if (GEP->isInBounds()) { + NonNullOrPoisonValues.insert(GEP); + } + } + } + } + + // Find phis that are derived entirely from nonnull-or-poison values, + // including other phis that are themselves derived entirely from these + // values. + for (auto &BB : F) { + for (auto &I : BB) { + auto *PN = dyn_cast<PHINode>(&I); + if (!PN) + break; + + SmallPhiSet VisitedPHIs; + if (isNonNullOrPoisonPhi(&VisitedPHIs, PN)) + NonNullOrPoisonValues.insert(PN); + } + } + + for (auto &BB : F) { + // This could also be extended to handle SwitchInst, but using a SwitchInst + // for a null check seems unlikely. + auto *BI = dyn_cast<BranchInst>(BB.getTerminator()); + if (!BI || BI->isUnconditional()) + continue; + + // The first operand of a conditional branch is the condition. + auto result = findNullCheck(&BI->getOperandUse(0)); + if (!result.first) + continue; + assert((result.second == NullCheckEq || result.second == NullCheckNe) && + "valid null check kind expected if ICmpInst was found"); + + BasicBlock *NonNullBB; + if (result.second == NullCheckEq) { + // If the comparison instruction is checking for equaliity with null, + // then the pointer is nonnull on the `false` branch. + NonNullBB = BI->getSuccessor(1); + } else { + // Otherwise, if the comparison instruction is checking for inequality + // with null, the pointer is nonnull on the `true` branch. + NonNullBB = BI->getSuccessor(0); + } + + Use *U = result.first; + ICmpInst *CI = cast<ICmpInst>(U->get()); + unsigned nonConstantIndex; + if (isa<Constant>(CI->getOperand(0))) + nonConstantIndex = 1; + else + nonConstantIndex = 0; + + // Due to the semantics of poison values in LLVM, we have to check that + // there is actually some externally visible side effect that is dependent + // on the poison value. Since poison values are otherwise treated as undef, + // and a load of undef is undefined behavior (which is externally visible), + // it suffices to look for a load of the nonnull-or-poison value. + // + // This could be extended to any block control-dependent on this branch of + // the null check, it's unclear if that will actually catch more cases in + // real code. + Value *PtrV = CI->getOperand(nonConstantIndex); + if (blockContainsLoadDerivedFrom(NonNullBB, PtrV)) { + Type *BoolTy = CI->getType(); + Value *NewV = ConstantInt::get(BoolTy, result.second == NullCheckNe); + U->set(NewV); + } + } + + NonNullOrPoisonValues.clear(); + + return Changed; +} + +/// Checks whether a phi is derived from known nonnnull-or-poison values, +/// including other phis that are derived from the same. May return `false` +/// conservatively in some cases, e.g. if exploring a large cycle of phis. +bool +NullCheckElimination::isNonNullOrPoisonPhi(SmallPhiSet *VisitedPhis, + PHINode *PN) { + // If we've already seen this phi, return `true`, even though it may not be + // nonnull, since some other operand in a cycle of phis may invalidate the + // optimistic assumption that the entire cycle is nonnull, including this phi. + if (!VisitedPhis->insert(PN).second) + return true; + + // Use a sensible limit to avoid iterating over long chains of phis that are + // unlikely to be nonnull. + if (VisitedPhis->size() >= kPhiLimit) + return false; + + unsigned numOperands = PN->getNumOperands(); + for (unsigned i = 0; i < numOperands; ++i) { + Value *SrcValue = PN->getOperand(i); + if (NonNullOrPoisonValues.count(SrcValue)) { + continue; + } else if (auto *SrcPN = dyn_cast<PHINode>(SrcValue)) { + if (!isNonNullOrPoisonPhi(VisitedPhis, SrcPN)) + return false; + } else { + return false; + } + } + + return true; +} + +/// Determines whether an ICmpInst is a null check of a known nonnull-or-poison +/// value. +NullCheckElimination::NullCheckResult +NullCheckElimination::isCmpNullCheck(ICmpInst *CI) { + if (!CI->isEquality()) + return NotNullCheck; + + unsigned constantIndex; + if (NonNullOrPoisonValues.count(CI->getOperand(0))) + constantIndex = 1; + else if (NonNullOrPoisonValues.count(CI->getOperand(1))) + constantIndex = 0; + else + return NotNullCheck; + + auto *C = dyn_cast<Constant>(CI->getOperand(constantIndex)); + if (!C || !C->isZeroValue()) + return NotNullCheck; + + return + CI->getPredicate() == llvm::CmpInst::ICMP_EQ ? NullCheckEq : NullCheckNe; +} + +/// Finds the Use, if any, of an ICmpInst null check of a nonnull-or-poison +/// value. +std::pair<Use*, NullCheckElimination::NullCheckResult> +NullCheckElimination::findNullCheck(Use *U) { + auto *I = dyn_cast<Instruction>(U->get()); + if (!I) + return std::make_pair(nullptr, NotNullCheck); + + if (auto *CI = dyn_cast<ICmpInst>(I)) { + NullCheckResult result = isCmpNullCheck(CI); + if (result == NotNullCheck) + return std::make_pair(nullptr, NotNullCheck); + else + return std::make_pair(U, result); + } + + unsigned opcode = I->getOpcode(); + if (opcode == Instruction::Or || opcode == Instruction::And) { + auto result = findNullCheck(&I->getOperandUse(0)); + if (result.second == NotNullCheck) + return findNullCheck(&I->getOperandUse(1)); + else + return result; + } + + return std::make_pair(nullptr, NotNullCheck); +} + +/// Determines whether `BB` contains a load from `PtrV`, or any inbounds GEP +/// derived from `PtrV`. +bool +NullCheckElimination::blockContainsLoadDerivedFrom(BasicBlock *BB, + Value *PtrV) { + for (auto &I : *BB) { + auto *LI = dyn_cast<LoadInst>(&I); + if (!LI) + continue; + + Value *V = LI->getPointerOperand(); + while (NonNullOrPoisonValues.count(V)) { + if (V == PtrV) + return true; + + auto *GEP = dyn_cast<GetElementPtrInst>(V); + if (!GEP) + break; + + V = GEP->getOperand(0); + } + } + + return false; +} + diff --git a/lib/Transforms/Scalar/Scalar.cpp b/lib/Transforms/Scalar/Scalar.cpp index a16e9e29a1f1..8dcd6720597d 100644 --- a/lib/Transforms/Scalar/Scalar.cpp +++ b/lib/Transforms/Scalar/Scalar.cpp @@ -69,6 +69,7 @@ void llvm::initializeScalarOpts(PassRegistry &Registry) { initializeTailCallElimPass(Registry); initializeSeparateConstOffsetFromGEPPass(Registry); initializeLoadCombinePass(Registry); + initializeNullCheckEliminationPass(Registry); } void LLVMInitializeScalarOpts(LLVMPassRegistryRef R) { diff --git a/test/Transforms/NullCheckElimination/basic.ll b/test/Transforms/NullCheckElimination/basic.ll new file mode 100644 index 000000000000..f176fb32c3fd --- /dev/null +++ b/test/Transforms/NullCheckElimination/basic.ll @@ -0,0 +1,165 @@ +; RUN: opt < %s -null-check-elimination -instsimplify -S | FileCheck %s + +define i64 @test_arg_simple(i64* nonnull %p) { +entry: + br label %loop_body + +loop_body: + %p0 = phi i64* [ %p, %entry ], [ %p1, %match_else ] + %b0 = icmp eq i64* %p0, null + br i1 %b0, label %return, label %match_else + +; CHECK-LABEL: @test_arg_simple +; CHECK-NOT: , null + +match_else: + %i0 = load i64* %p0 + %p1 = getelementptr inbounds i64* %p0, i64 1 + %b1 = icmp ugt i64 %i0, 10 + br i1 %b1, label %return, label %loop_body + +return: + ret i64 0 +} + +define i64 @test_arg_simple_fail(i64* %p) { +entry: + br label %loop_body + +loop_body: + %p0 = phi i64* [ %p, %entry ], [ %p1, %match_else ] + %b0 = icmp eq i64* %p0, null + br i1 %b0, label %return, label %match_else + +; CHECK-LABEL: @test_arg_simple_fail +; CHECK: null + +match_else: + %i0 = load i64* %p0 + %p1 = getelementptr inbounds i64* %p0, i64 1 + %b1 = icmp ugt i64 %i0, 10 + br i1 %b1, label %return, label %loop_body + +return: + ret i64 0 +} + +define i64 @test_inbounds_simple(i64* %p) { +entry: + %p0 = getelementptr inbounds i64* %p, i64 0 + br label %loop_body + +loop_body: + %p1 = phi i64* [ %p0, %entry ], [ %p2, %match_else ] + %b0 = icmp eq i64* %p1, null + br i1 %b0, label %return, label %match_else + +; CHECK-LABEL: @test_inbounds_simple +; CHECK-NOT: null + +match_else: + %i0 = load i64* %p1 + %p2 = getelementptr inbounds i64* %p1, i64 1 + %b1 = icmp ugt i64 %i0, 10 + br i1 %b1, label %return, label %loop_body + +return: + ret i64 0 +} + +define i64 @test_inbounds_simple_fail(i64* %p) { +entry: + %p0 = getelementptr i64* %p, i64 0 + br label %loop_body + +loop_body: + %p1 = phi i64* [ %p0, %entry ], [ %p2, %match_else ] + %b0 = icmp eq i64* %p1, null + br i1 %b0, label %return, label %match_else + +; CHECK-LABEL: @test_inbounds_simple_fail +; CHECK: null + +match_else: + %i0 = load i64* %p1 + %p2 = getelementptr inbounds i64* %p1, i64 1 + %b1 = icmp ugt i64 %i0, 10 + br i1 %b1, label %return, label %loop_body + +return: + ret i64 0 +} + +define i64 @test_inbounds_or(i64* %p, i64* %q) { +entry: + %p0 = getelementptr inbounds i64* %p, i64 0 + br label %loop_body + +loop_body: + %p1 = phi i64* [ %p0, %entry ], [ %p2, %match_else ] + %b0 = icmp eq i64* %p1, %q + %b1 = icmp eq i64* %p1, null + %b2 = or i1 %b0, %b1 + br i1 %b2, label %return, label %match_else + +; CHECK-LABEL: @test_inbounds_or +; CHECK-NOT: null + +match_else: + %i0 = load i64* %p1 + %p2 = getelementptr inbounds i64* %p1, i64 1 + %b3 = icmp ugt i64 %i0, 10 + br i1 %b3, label %return, label %loop_body + +return: + ret i64 0 +} + +define i64 @test_inbounds_and(i64* %p, i64* %q) { +entry: + %p0 = getelementptr inbounds i64* %p, i64 0 + br label %loop_body + +loop_body: + %p1 = phi i64* [ %p0, %entry ], [ %p2, %match_else ] + %b0 = icmp eq i64* %p1, %q + %b1 = icmp eq i64* %p1, null + %b2 = and i1 %b0, %b1 + br i1 %b2, label %return, label %match_else + +; CHECK-LABEL: @test_inbounds_and +; CHECK-NOT: null + +match_else: + %i0 = load i64* %p1 + %p2 = getelementptr inbounds i64* %p1, i64 1 + %b3 = icmp ugt i64 %i0, 10 + br i1 %b3, label %return, label %loop_body + +return: + ret i64 0 +} + +define i64 @test_inbounds_derived_load(i64* %p) { +entry: + %p0 = getelementptr inbounds i64* %p, i64 0 + br label %loop_body + +loop_body: + %p1 = phi i64* [ %p0, %entry ], [ %p2, %match_else ] + %b0 = icmp eq i64* %p1, null + br i1 %b0, label %return, label %match_else + +; CHECK-LABEL: @test_inbounds_derived_load +; CHECK-NOT: null + +match_else: + %p2 = getelementptr inbounds i64* %p1, i64 1 + %i0 = load i64* %p2 + %b1 = icmp ugt i64 %i0, 10 + br i1 %b1, label %return, label %loop_body + +return: + ret i64 0 +} + From a1938c6c5d619f67b307f3a772fab5ee4f9c4218 Mon Sep 17 00:00:00 2001 From: Cameron Zwarich <zwarich@mozilla.com> Date: Thu, 26 Jun 2014 23:41:06 -0700 Subject: [PATCH 5/6] Add the NullCheckElimination pass to the default pass list Since the NullCheckElimination pass has a similar intent to the CorrelatedValuePropagation pass, I decided to run it right after the both places that the latter runs. --- lib/Transforms/IPO/PassManagerBuilder.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/Transforms/IPO/PassManagerBuilder.cpp b/lib/Transforms/IPO/PassManagerBuilder.cpp index 0414caa61fca..906d56fa6dcc 100644 --- a/lib/Transforms/IPO/PassManagerBuilder.cpp +++ b/lib/Transforms/IPO/PassManagerBuilder.cpp @@ -220,6 +220,8 @@ void PassManagerBuilder::populateModulePassManager(PassManagerBase &MPM) { MPM.add(createEarlyCSEPass()); // Catch trivial redundancies MPM.add(createJumpThreadingPass()); // Thread jumps. MPM.add(createCorrelatedValuePropagationPass()); // Propagate conditionals + // Specific to the rust-lang llvm branch: + MPM.add(createNullCheckEliminationPass()); // Eliminate null checks MPM.add(createCFGSimplificationPass()); // Merge & remove BBs MPM.add(createInstructionCombiningPass()); // Combine silly seq's addExtensionsToPM(EP_Peephole, MPM); @@ -255,6 +257,8 @@ void PassManagerBuilder::populateModulePassManager(PassManagerBase &MPM) { addExtensionsToPM(EP_Peephole, MPM); MPM.add(createJumpThreadingPass()); // Thread jumps MPM.add(createCorrelatedValuePropagationPass()); + // Specific to the rust-lang llvm branch: + MPM.add(createNullCheckEliminationPass()); // Eliminate null checks MPM.add(createDeadStoreEliminationPass()); // Delete dead stores addExtensionsToPM(EP_ScalarOptimizerLate, MPM); From 32c27dda3ae2318b6897f00795009cd6f42ac4b3 Mon Sep 17 00:00:00 2001 From: Cameron Zwarich <zwarich@mozilla.com> Date: Sun, 29 Jun 2014 11:18:05 -0700 Subject: [PATCH 6/6] Improve the NullCheckElimination pass Teach the NullCheckElimination pass about recurrences involving inbounds GEPs. This allows the pass to optimize null checks from most uses of single slice and vector iterators. This still isn't sufficient to eliminate null checks from uses of multiple iterators with zip(). --- .../Scalar/NullCheckElimination.cpp | 373 ++++++++++++------ test/Transforms/NullCheckElimination/basic.ll | 120 ++++++ .../NullCheckElimination/complex.ll | 316 +++++++++++++++ 3 files changed, 688 insertions(+), 121 deletions(-) create mode 100644 test/Transforms/NullCheckElimination/complex.ll diff --git a/lib/Transforms/Scalar/NullCheckElimination.cpp b/lib/Transforms/Scalar/NullCheckElimination.cpp index 4f04bd65c204..f0fa3aed6ff9 100644 --- a/lib/Transforms/Scalar/NullCheckElimination.cpp +++ b/lib/Transforms/Scalar/NullCheckElimination.cpp @@ -34,78 +34,119 @@ namespace { private: static const unsigned kPhiLimit = 16; typedef SmallPtrSet<PHINode*, kPhiLimit> SmallPhiSet; - enum NullCheckResult { - NotNullCheck, - NullCheckEq, - NullCheckNe, + + enum CmpKind { + /// A null check of an unconditionally nonnull-or-poison value. + NullCheckDefiniteCmp, + + /// A null check of the phi representing a nontrivial inbounds recurrence, + /// which is not known to be unconditionally nonnull-or-poison. + NullCheckRecurrenceCmp, + + /// A comparison of the phi representing a nontrivial inbounds recurrence + /// with an inbounds GEP derived from the base of the recurrence, which + /// will typically represent a bound on the recurrence. + RecurrencePhiBoundCmp, + }; + + enum CmpPred { + CmpEq, + CmpNe, + }; + + struct CmpDesc { + CmpDesc(CmpKind k, CmpPred p, Use *u, Value *v) + : kind(k), pred(p), use(u), ptrValue(v) { } + + CmpKind kind; + CmpPred pred; + Use *use; + Value *ptrValue; }; + typedef SmallVector<CmpDesc, 4> CmpDescVec; + bool isNonNullOrPoisonPhi(SmallPhiSet *VisitedPhis, PHINode*); + Value *isNontrivialInBoundsRecurrence(PHINode*); - NullCheckResult isCmpNullCheck(ICmpInst*); - std::pair<Use*, NullCheckResult> findNullCheck(Use*); + bool classifyCmp(CmpDescVec*, Use*); + bool findRelevantCmps(CmpDescVec*, Use*); bool blockContainsLoadDerivedFrom(BasicBlock*, Value*); + /// Tracks values that are unconditionally nonnull-or-poison by definition, + /// but not values that are known nonnull-or-poison in a given context by + /// their uses, e.g. in recurrences. DenseSet<Value*> NonNullOrPoisonValues; + + /// Tracks values that are bases of nontrivial inbounds recurrences. + DenseSet<Value*> InBoundsRecurrenceBases; + + /// Maps phis that correspond to nontrivial inbounds recurrences to their + /// base values. + DenseMap<Value*, Value*> InBoundsRecurrenceBaseMap; }; } char NullCheckElimination::ID = 0; INITIALIZE_PASS_BEGIN(NullCheckElimination, - "null-check-elimination", - "Null Check Elimination", - false, false) + "null-check-elimination", + "Null Check Elimination", + false, false) INITIALIZE_PASS_END(NullCheckElimination, - "null-check-elimination", - "Null Check Elimination", - false, false) + "null-check-elimination", + "Null Check Elimination", + false, false) FunctionPass *llvm::createNullCheckEliminationPass() { return new NullCheckElimination(); } +static GetElementPtrInst *castToInBoundsGEP(Value *V) { + auto *GEP = dyn_cast<GetElementPtrInst>(V); + if (!GEP || !GEP->isInBounds()) + return nullptr; + return GEP; +} + +static bool isZeroConstant(Value *V) { + auto *C = dyn_cast<Constant>(V); + return C && C->isZeroValue(); +} + bool NullCheckElimination::runOnFunction(Function &F) { if (skipOptnoneFunction(F)) return false; bool Changed = false; - // Collect argumetns with the `nonnull` attribute. + // Collect arguments with the `nonnull` attribute. for (auto &Arg : F.args()) { if (Arg.hasNonNullAttr()) NonNullOrPoisonValues.insert(&Arg); } - // Collect instructions that definitely produce nonnull-or-poison values. - // At the moment, this is restricted to inbounds GEPs. It would be slightly - // more difficult to include uses of values dominated by a null check, since - // then we would have to consider uses instead of mere values. + // Collect instructions that definitely produce nonnull-or-poison values. At + // the moment, this is restricted to inbounds GEPs, and phis that are derived + // entirely from nonnull-or-poison-values (including other phis that are + // themselves derived from the same). for (auto &BB : F) { for (auto &I : BB) { - if (auto *GEP = dyn_cast<GetElementPtrInst>(&I)) { - if (GEP->isInBounds()) { - NonNullOrPoisonValues.insert(GEP); - } + if (auto *GEP = castToInBoundsGEP(&I)) { + NonNullOrPoisonValues.insert(GEP); + } else if (auto *PN = dyn_cast<PHINode>(&I)) { + SmallPhiSet VisitedPHIs; + if (isNonNullOrPoisonPhi(&VisitedPHIs, PN)) + NonNullOrPoisonValues.insert(PN); + + if (auto *BaseV = isNontrivialInBoundsRecurrence(PN)) { + InBoundsRecurrenceBases.insert(BaseV); + InBoundsRecurrenceBaseMap[PN] = BaseV; + } } } } - // Find phis that are derived entirely from nonnull-or-poison values, - // including other phis that are themselves derived entirely from these - // values. - for (auto &BB : F) { - for (auto &I : BB) { - auto *PN = dyn_cast<PHINode>(&I); - if (!PN) - break; - - SmallPhiSet VisitedPHIs; - if (isNonNullOrPoisonPhi(&VisitedPHIs, PN)) - NonNullOrPoisonValues.insert(PN); - } - } - for (auto &BB : F) { // This could also be extended to handle SwitchInst, but using a SwitchInst // for a null check seems unlikely. @@ -114,49 +155,75 @@ bool NullCheckElimination::runOnFunction(Function &F) { continue; // The first operand of a conditional branch is the condition. - auto result = findNullCheck(&BI->getOperandUse(0)); - if (!result.first) + CmpDescVec Cmps; + if (!findRelevantCmps(&Cmps, &BI->getOperandUse(0))) continue; - assert((result.second == NullCheckEq || result.second == NullCheckNe) && - "valid null check kind expected if ICmpInst was found"); - - BasicBlock *NonNullBB; - if (result.second == NullCheckEq) { - // If the comparison instruction is checking for equaliity with null, - // then the pointer is nonnull on the `false` branch. - NonNullBB = BI->getSuccessor(1); - } else { - // Otherwise, if the comparison instruction is checking for inequality - // with null, the pointer is nonnull on the `true` branch. - NonNullBB = BI->getSuccessor(0); - } - Use *U = result.first; - ICmpInst *CI = cast<ICmpInst>(U->get()); - unsigned nonConstantIndex; - if (isa<Constant>(CI->getOperand(0))) - nonConstantIndex = 1; - else - nonConstantIndex = 0; - - // Due to the semantics of poison values in LLVM, we have to check that - // there is actually some externally visible side effect that is dependent - // on the poison value. Since poison values are otherwise treated as undef, - // and a load of undef is undefined behavior (which is externally visible), - // it suffices to look for a load of the nonnull-or-poison value. - // - // This could be extended to any block control-dependent on this branch of - // the null check, it's unclear if that will actually catch more cases in - // real code. - Value *PtrV = CI->getOperand(nonConstantIndex); - if (blockContainsLoadDerivedFrom(NonNullBB, PtrV)) { - Type *BoolTy = CI->getType(); - Value *NewV = ConstantInt::get(BoolTy, result.second == NullCheckNe); - U->set(NewV); + for (auto &Cmp : Cmps) { + // We are only tracking comparisons of inbounds recurrence phis with their + // bounds so that we can eliminate null checks based on them, which are of + // kind NullCheckRecurrenceCmp. We can't use a lone RecurrencePhiBoundCmp + // to perform any optimizations. + if (Cmp.kind == RecurrencePhiBoundCmp) + continue; + + if (Cmp.kind == NullCheckRecurrenceCmp) { + // Look for a matching RecurrencePhiBoundCmp. If one exists, then we can + // be sure that this branch condition depends on the recurrence. Since + // both the bounds and the recurrence successor value are inbounds, and + // they are both derived from the base, the base being null would imply + // that the bounds and recurrence successor values are poison. + bool FoundMatchingCmp = false; + for (auto &OtherCmp : Cmps) { + if (OtherCmp.kind == RecurrencePhiBoundCmp && + OtherCmp.ptrValue == Cmp.ptrValue) { + FoundMatchingCmp = true; + break; + } + } + if (!FoundMatchingCmp) + continue; + } + + BasicBlock *NonNullBB; + if (Cmp.pred == CmpEq) { + // If the comparison instruction is checking for equality with null then + // the pointer is nonnull on the `false` branch. + NonNullBB = BI->getSuccessor(1); + } else { + // Otherwise, if the comparison instruction is checking for inequality + // with null, the pointer is nonnull on the `true` branch. + NonNullBB = BI->getSuccessor(0); + } + + // This is a crude approximation of control dependence: if the branch + // target has a single predecessor edge, then it must be control- + // dependent on the branch. + if (!NonNullBB->getSinglePredecessor()) + continue; + + // Due to the semantics of poison values in LLVM, we have to check that + // there is actually some externally visible side effect that is dependent + // on the poison value. Since poison values are otherwise treated as + // undef, and a load of undef is undefined behavior (which is externally + // visible), it suffices to look for a load of the nonnull-or-poison + // value. + // + // This could be extended to any block control-dependent on this branch of + // the null check, it's unclear if that will actually catch more cases in + // real code. + if (blockContainsLoadDerivedFrom(NonNullBB, Cmp.ptrValue)) { + Type *BoolTy = Type::getInt1Ty(F.getContext()); + Value *NewV = ConstantInt::get(BoolTy, Cmp.pred == CmpNe); + Cmp.use->set(NewV); + Changed = true; + } } } NonNullOrPoisonValues.clear(); + InBoundsRecurrenceBases.clear(); + InBoundsRecurrenceBaseMap.clear(); return Changed; } @@ -164,9 +231,12 @@ bool NullCheckElimination::runOnFunction(Function &F) { /// Checks whether a phi is derived from known nonnnull-or-poison values, /// including other phis that are derived from the same. May return `false` /// conservatively in some cases, e.g. if exploring a large cycle of phis. +/// +/// This function may also insert any inbounds GEPs that it finds into +/// NonNullOrPoisonValues. bool NullCheckElimination::isNonNullOrPoisonPhi(SmallPhiSet *VisitedPhis, - PHINode *PN) { + PHINode *PN) { // If we've already seen this phi, return `true`, even though it may not be // nonnull, since some other operand in a cycle of phis may invalidate the // optimistic assumption that the entire cycle is nonnull, including this phi. @@ -183,9 +253,11 @@ NullCheckElimination::isNonNullOrPoisonPhi(SmallPhiSet *VisitedPhis, Value *SrcValue = PN->getOperand(i); if (NonNullOrPoisonValues.count(SrcValue)) { continue; + } else if (auto *GEP = castToInBoundsGEP(SrcValue)) { + NonNullOrPoisonValues.insert(GEP); } else if (auto *SrcPN = dyn_cast<PHINode>(SrcValue)) { if (!isNonNullOrPoisonPhi(VisitedPhis, SrcPN)) - return false; + return false; } else { return false; } @@ -194,75 +266,134 @@ NullCheckElimination::isNonNullOrPoisonPhi(SmallPhiSet *VisitedPhis, return true; } -/// Determines whether an ICmpInst is a null check of a known nonnull-or-poison -/// value. -NullCheckElimination::NullCheckResult -NullCheckElimination::isCmpNullCheck(ICmpInst *CI) { +/// Determines whether a phi corresponds to an inbounds recurrence where the +/// base is not a known nonnull-or-poison value. Returns the base value, or +/// null if the phi doesn't correspond to such a recurrence. +Value *NullCheckElimination::isNontrivialInBoundsRecurrence(PHINode *PN) { + if (PN->getNumOperands() != 2) + return nullptr; + + Value *BaseV; + GetElementPtrInst *SuccessorI; + if (auto *GEP = castToInBoundsGEP(PN->getOperand(0))) { + BaseV = PN->getOperand(1); + SuccessorI = GEP; + } else if (auto *GEP = castToInBoundsGEP(PN->getOperand(1))) { + BaseV = PN->getOperand(0); + SuccessorI = GEP; + } else { + return nullptr; + } + + if (NonNullOrPoisonValues.count(BaseV) || SuccessorI->getOperand(0) != PN) + return nullptr; + + return BaseV; +} + +/// Determines whether an ICmpInst is one of the forms that is relevant to +/// null check elimination, and then adds a CmpDesc to Cmps when applicable. +/// The ICmpInst is passed as a Use so this Use can be placed into the CmpDesc, +/// but the Use parameter must be a Use of an ICmpInst. +bool NullCheckElimination::classifyCmp(CmpDescVec *Cmps, Use *U) { + auto *CI = cast<ICmpInst>(U); if (!CI->isEquality()) - return NotNullCheck; - - unsigned constantIndex; - if (NonNullOrPoisonValues.count(CI->getOperand(0))) - constantIndex = 1; - else if (NonNullOrPoisonValues.count(CI->getOperand(1))) - constantIndex = 0; - else - return NotNullCheck; - - auto *C = dyn_cast<Constant>(CI->getOperand(constantIndex)); - if (!C || !C->isZeroValue()) - return NotNullCheck; - - return - CI->getPredicate() == llvm::CmpInst::ICMP_EQ ? NullCheckEq : NullCheckNe; + return false; + + CmpPred Pred = (CI->getPredicate() == llvm::CmpInst::ICMP_EQ) ? CmpEq : CmpNe; + Value *Op0 = CI->getOperand(0); + Value *Op1 = CI->getOperand(1); + + if (NonNullOrPoisonValues.count(Op0)) { + if (isZeroConstant(Op1)) { + Cmps->push_back(CmpDesc(NullCheckDefiniteCmp, Pred, U, Op0)); + return true; + } + + auto it = InBoundsRecurrenceBaseMap.find(Op1); + if (it == InBoundsRecurrenceBaseMap.end()) + return false; + + auto *GEP = castToInBoundsGEP(Op0); + if (!GEP) + return false; + + auto *BaseV = it->second; + if (GEP->getOperand(0) != BaseV) + return false; + + Cmps->push_back(CmpDesc(RecurrencePhiBoundCmp, Pred, U, Op1)); + return true; + } + + // Since InstCombine or InstSimplify should have canonicalized a comparison + // with `null` to have the `null` in the second operand, we don't need to + // handle the case where Op0 is `null` like we did with Op1 above. + if (NonNullOrPoisonValues.count(Op1)) { + auto it = InBoundsRecurrenceBaseMap.find(Op0); + if (it == InBoundsRecurrenceBaseMap.end()) + return false; + + auto *GEP = castToInBoundsGEP(Op1); + if (!GEP) + return false; + + auto *BaseV = it->second; + if (GEP->getOperand(0) != BaseV) + return false; + + Cmps->push_back(CmpDesc(RecurrencePhiBoundCmp, Pred, U, Op0)); + return true; + } + + if (InBoundsRecurrenceBaseMap.count(Op0)) { + if (isZeroConstant(Op1)) { + Cmps->push_back(CmpDesc(NullCheckRecurrenceCmp, Pred, U, Op0)); + return true; + } + } + + return false; } -/// Finds the Use, if any, of an ICmpInst null check of a nonnull-or-poison -/// value. -std::pair<Use*, NullCheckElimination::NullCheckResult> -NullCheckElimination::findNullCheck(Use *U) { +/// Classifies the comparisons that are relevant to null check elimination, +/// starting from a Use. The CmpDescs of the comparisons are collected in Cmps. +bool NullCheckElimination::findRelevantCmps(CmpDescVec *Cmps, Use *U) { auto *I = dyn_cast<Instruction>(U->get()); if (!I) - return std::make_pair(nullptr, NotNullCheck); - - if (auto *CI = dyn_cast<ICmpInst>(I)) { - NullCheckResult result = isCmpNullCheck(CI); - if (result == NotNullCheck) - return std::make_pair(nullptr, NotNullCheck); - else - return std::make_pair(U, result); - } + return false; - unsigned opcode = I->getOpcode(); - if (opcode == Instruction::Or || opcode == Instruction::And) { - auto result = findNullCheck(&I->getOperandUse(0)); - if (result.second == NotNullCheck) - return findNullCheck(&I->getOperandUse(1)); - else - return result; + if (isa<ICmpInst>(I)) + return classifyCmp(Cmps, U); + + unsigned Opcode = I->getOpcode(); + if (Opcode == Instruction::Or || Opcode == Instruction::And) { + bool FoundCmps = findRelevantCmps(Cmps, &I->getOperandUse(0)); + FoundCmps |= findRelevantCmps(Cmps, &I->getOperandUse(1)); + return FoundCmps; } - return std::make_pair(nullptr, NotNullCheck); + return false; } /// Determines whether `BB` contains a load from `PtrV`, or any inbounds GEP /// derived from `PtrV`. bool NullCheckElimination::blockContainsLoadDerivedFrom(BasicBlock *BB, - Value *PtrV) { + Value *PtrV) { for (auto &I : *BB) { auto *LI = dyn_cast<LoadInst>(&I); if (!LI) continue; Value *V = LI->getPointerOperand(); - while (NonNullOrPoisonValues.count(V)) { + while (1) { if (V == PtrV) - return true; + return true; - auto *GEP = dyn_cast<GetElementPtrInst>(V); + auto *GEP = castToInBoundsGEP(V); if (!GEP) - break; + break; V = GEP->getOperand(0); } diff --git a/test/Transforms/NullCheckElimination/basic.ll b/test/Transforms/NullCheckElimination/basic.ll index f176fb32c3fd..8ea5851f627a 100644 --- a/test/Transforms/NullCheckElimination/basic.ll +++ b/test/Transforms/NullCheckElimination/basic.ll @@ -22,6 +22,28 @@ return: ret i64 0 } +define i64 @test_arg_simple_ne(i64* nonnull %p) { +entry: + br label %loop_body + +loop_body: + %p0 = phi i64* [ %p, %entry ], [ %p1, %match_else ] + %b0 = icmp ne i64* %p0, null + br i1 %b0, label %match_else, label %return + +; CHECK-LABEL: @test_arg_simple +; CHECK-NOT: , null + +match_else: + %i0 = load i64* %p0 + %p1 = getelementptr inbounds i64* %p0, i64 1 + %b1 = icmp ugt i64 %i0, 10 + br i1 %b1, label %return, label %loop_body + +return: + ret i64 0 +} + define i64 @test_arg_simple_fail(i64* %p) { entry: br label %loop_body @@ -44,6 +66,31 @@ return: ret i64 0 } +define i64 @test_arg_simple_fail_control_dep(i64* nonnull %p) { +entry: + br label %loop_body + +loop_body: + %p0 = phi i64* [ %p, %entry ], [ %p1, %match_else ] + br i1 undef, label %loop_body2, label %match_else + +loop_body2: + %b0 = icmp eq i64* %p0, null + br i1 %b0, label %return, label %match_else + +; CHECK-LABEL: @test_arg_simple_fail_control_dep +; CHECK: null + +match_else: + %i0 = load i64* %p0 + %p1 = getelementptr inbounds i64* %p0, i64 1 + %b1 = icmp ugt i64 %i0, 10 + br i1 %b1, label %return, label %loop_body + +return: + ret i64 0 +} + define i64 @test_inbounds_simple(i64* %p) { entry: %p0 = getelementptr inbounds i64* %p, i64 0 @@ -67,6 +114,29 @@ return: ret i64 0 } +define i64 @test_inbounds_simple_ne(i64* %p) { +entry: + %p0 = getelementptr inbounds i64* %p, i64 0 + br label %loop_body + +loop_body: + %p1 = phi i64* [ %p0, %entry ], [ %p2, %match_else ] + %b0 = icmp ne i64* %p1, null + br i1 %b0, label %match_else, label %return + +; CHECK-LABEL: @test_inbounds_simple +; CHECK-NOT: null + +match_else: + %i0 = load i64* %p1 + %p2 = getelementptr inbounds i64* %p1, i64 1 + %b1 = icmp ugt i64 %i0, 10 + br i1 %b1, label %return, label %loop_body + +return: + ret i64 0 +} + define i64 @test_inbounds_simple_fail(i64* %p) { entry: %p0 = getelementptr i64* %p, i64 0 @@ -90,6 +160,32 @@ return: ret i64 0 } +define i64 @test_inbounds_simple_fail_control_dep(i64* %p) { +entry: + %p0 = getelementptr i64* %p, i64 0 + br label %loop_body + +loop_body: + %p1 = phi i64* [ %p0, %entry ], [ %p2, %match_else ] + br i1 undef, label %loop_body2, label %match_else + +loop_body2: + %b0 = icmp eq i64* %p1, null + br i1 %b0, label %return, label %match_else + +; CHECK-LABEL: @test_inbounds_simple_fail_control_dep +; CHECK: null + +match_else: + %i0 = load i64* %p1 + %p2 = getelementptr inbounds i64* %p1, i64 1 + %b1 = icmp ugt i64 %i0, 10 + br i1 %b1, label %return, label %loop_body + +return: + ret i64 0 +} + define i64 @test_inbounds_or(i64* %p, i64* %q) { entry: %p0 = getelementptr inbounds i64* %p, i64 0 @@ -163,3 +259,27 @@ return: ret i64 0 } +define i64 @test_inbounds_derived_load_fail(i64* %p) { +entry: + %p0 = getelementptr inbounds i64* %p, i64 0 + br label %loop_body + +loop_body: + %p1 = phi i64* [ %p0, %entry ], [ %p2, %match_else ] + %b0 = icmp eq i64* %p1, null + br i1 %b0, label %return, label %match_else + +; CHECK-LABEL: @test_inbounds_derived_load_fail +; CHECK: icmp eq i64* %p1, null + +match_else: + %p2 = getelementptr inbounds i64* %p1, i64 1 + %p3 = getelementptr i64* %p1, i64 1 + %i0 = load i64* %p3 + %b1 = icmp ugt i64 %i0, 10 + br i1 %b1, label %return, label %loop_body + +return: + ret i64 0 +} + diff --git a/test/Transforms/NullCheckElimination/complex.ll b/test/Transforms/NullCheckElimination/complex.ll new file mode 100644 index 000000000000..3059d02ad8dc --- /dev/null +++ b/test/Transforms/NullCheckElimination/complex.ll @@ -0,0 +1,316 @@ +; RUN: opt < %s -null-check-elimination -instsimplify -S | FileCheck %s + +define i64 @test_arg_multiple(i64* nonnull %p, i64* nonnull %q) { +entry: + br label %loop_body + +loop_body: + %p0 = phi i64* [ %p, %entry ], [ %p1, %match_else ] + %q0 = phi i64* [ %q, %entry ], [ %q1, %match_else ] + %b0 = icmp eq i64* %p0, null + %b1 = icmp eq i64* %q0, null + %b2 = or i1 %b0, %b1 + br i1 %b2, label %return, label %match_else + +; CHECK-LABEL: @test_arg_multiple +; CHECK-NOT: , null + +match_else: + %i0 = load i64* %p0 + %i1 = load i64* %q0 + %p1 = getelementptr inbounds i64* %p0, i64 1 + %q1 = getelementptr inbounds i64* %q0, i64 1 + %b3 = icmp ugt i64 %i0, 10 + br i1 %b3, label %return, label %loop_body + +return: + ret i64 0 +} + +define i64 @test_arg_multiple_fail(i64* nonnull %p, i64* %q) { +entry: + br label %loop_body + +loop_body: + %p0 = phi i64* [ %p, %entry ], [ %p1, %match_else ] + %q0 = phi i64* [ %q, %entry ], [ %q1, %match_else ] + %b0 = icmp eq i64* %p0, null + %b1 = icmp eq i64* %q0, null + %b2 = or i1 %b0, %b1 + br i1 %b2, label %return, label %match_else + +; CHECK-LABEL: @test_arg_multiple_fail +; CHECK-NOT: icmp eq i64* %p0, null +; CHECK: icmp eq i64* %q0, null + +match_else: + %i0 = load i64* %p0 + %i1 = load i64* %q0 + %p1 = getelementptr inbounds i64* %p0, i64 1 + %q1 = getelementptr inbounds i64* %q0, i64 1 + %b3 = icmp ugt i64 %i0, 10 + br i1 %b3, label %return, label %loop_body + +return: + ret i64 0 +} + +define i64 @test_inbounds_multiple(i64* %p, i64* %q) { +entry: + %p0 = getelementptr inbounds i64* %p, i64 0 + %q0 = getelementptr inbounds i64* %q, i64 0 + br label %loop_body + +loop_body: + %p1 = phi i64* [ %p0, %entry ], [ %p2, %match_else ] + %q1 = phi i64* [ %q0, %entry ], [ %q2, %match_else ] + %b0 = icmp eq i64* %p1, null + %b1 = icmp eq i64* %q1, null + %b2 = or i1 %b0, %b1 + br i1 %b2, label %return, label %match_else + +; CHECK-LABEL: @test_inbounds_multiple +; CHECK-NOT: , null + +match_else: + %i0 = load i64* %p1 + %i1 = load i64* %q1 + %p2 = getelementptr inbounds i64* %p1, i64 1 + %q2 = getelementptr inbounds i64* %q1, i64 1 + %b3 = icmp ugt i64 %i0, 10 + br i1 %b3, label %return, label %loop_body + +return: + ret i64 0 +} + +define i64 @test_inbounds_multiple_fail(i64* %p, i64* %q) { +entry: + %p0 = getelementptr i64* %p, i64 0 + %q0 = getelementptr inbounds i64* %q, i64 0 + br label %loop_body + +loop_body: + %p1 = phi i64* [ %p0, %entry ], [ %p2, %match_else ] + %q1 = phi i64* [ %q0, %entry ], [ %q2, %match_else ] + %b0 = icmp eq i64* %p1, null + %b1 = icmp eq i64* %q1, null + %b2 = or i1 %b0, %b1 + br i1 %b2, label %return, label %match_else + +; CHECK-LABEL: @test_inbounds_multiple_fail +; CHECK: icmp eq i64* %p1, null +; CHECK-NOT: icmp eq i64* %q1, null + +match_else: + %i0 = load i64* %p1 + %i1 = load i64* %q1 + %p2 = getelementptr inbounds i64* %p1, i64 1 + %q2 = getelementptr inbounds i64* %q1, i64 1 + %b3 = icmp ugt i64 %i0, 10 + br i1 %b3, label %return, label %loop_body + +return: + ret i64 0 +} + +define i64 @test_inbounds_recurrence_or(i64* %p, i64 %len) { +entry: + %q = getelementptr inbounds i64* %p, i64 %len + br label %loop_body + +loop_body: + %p0 = phi i64* [ %p, %entry ], [ %p1, %match_else ] + %b0 = icmp eq i64* %p0, %q + %b1 = icmp eq i64* %p0, null + %b2 = or i1 %b0, %b1 + br i1 %b2, label %return, label %match_else + +; CHECK-LABEL: @test_inbounds_recurrence_or +; CHECK-NOT: null + +match_else: + %i0 = load i64* %p0 + %p1 = getelementptr inbounds i64* %p0, i64 1 + %b3 = icmp ugt i64 %i0, 10 + br i1 %b3, label %return, label %loop_body + +return: + ret i64 0 +} + +define i64 @test_inbounds_recurrence_or_fail1(i64* %p, i64 %len) { +entry: + %q = getelementptr i64* %p, i64 %len + br label %loop_body + +loop_body: + %p0 = phi i64* [ %p, %entry ], [ %p1, %match_else ] + %b0 = icmp eq i64* %p0, %q + %b1 = icmp eq i64* %p0, null + %b2 = or i1 %b0, %b1 + br i1 %b2, label %return, label %match_else + +; CHECK-LABEL: @test_inbounds_recurrence_or_fail1 +; CHECK: icmp eq i64* %p0, null + +match_else: + %i0 = load i64* %p0 + %p1 = getelementptr inbounds i64* %p0, i64 1 + %b3 = icmp ugt i64 %i0, 10 + br i1 %b3, label %return, label %loop_body + +return: + ret i64 0 +} + +define i64 @test_inbounds_recurrence_or_fail2(i64* %p, i64 %len) { +entry: + %q = getelementptr inbounds i64* %p, i64 %len + br label %loop_body + +loop_body: + %p0 = phi i64* [ %p, %entry ], [ %p1, %match_else ] + %b0 = icmp eq i64* %p0, %q + %b1 = icmp eq i64* %p0, null + %b2 = or i1 %b0, %b1 + br i1 %b2, label %return, label %match_else + +; CHECK-LABEL: @test_inbounds_recurrence_or_fail2 +; CHECK: icmp eq i64* %p0, null + +match_else: + %i0 = load i64* %p0 + %p1 = getelementptr i64* %p0, i64 1 + %b3 = icmp ugt i64 %i0, 10 + br i1 %b3, label %return, label %loop_body + +return: + ret i64 0 +} + +define i64 @test_inbounds_recurrence_and(i64* %p, i64 %len) { +entry: + %q = getelementptr inbounds i64* %p, i64 %len + br label %loop_body + +loop_body: + %p0 = phi i64* [ %p, %entry ], [ %p1, %match_else ] + %b0 = icmp eq i64* %p0, %q + %b1 = icmp eq i64* %p0, null + %b2 = or i1 %b0, %b1 + br i1 %b2, label %return, label %match_else + +; CHECK-LABEL: @test_inbounds_recurrence_and +; CHECK-NOT: null + +match_else: + %i0 = load i64* %p0 + %p1 = getelementptr inbounds i64* %p0, i64 1 + %b3 = icmp ugt i64 %i0, 10 + br i1 %b3, label %return, label %loop_body + +return: + ret i64 0 +} + +define i64 @test_inbounds_recurrence_or_rev(i64* %p, i64 %len) { +entry: + %q = getelementptr inbounds i64* %p, i64 %len + br label %loop_body + +loop_body: + %p0 = phi i64* [ %p1, %match_else ], [ %p, %entry ] + %b0 = icmp eq i64* %p0, null + %b1 = icmp eq i64* %q, %p0 + %b2 = or i1 %b0, %b1 + br i1 %b2, label %return, label %match_else + +; CHECK-LABEL: @test_inbounds_recurrence_or_rev +; CHECK-NOT: null + +match_else: + %i0 = load i64* %p0 + %p1 = getelementptr inbounds i64* %p0, i64 1 + %b3 = icmp ugt i64 %i0, 10 + br i1 %b3, label %return, label %loop_body + +return: + ret i64 0 +} + +define i64 @test_inbounds_recurrence_or_rev_fail1(i64* %p, i64 %len) { +entry: + %q = getelementptr i64* %p, i64 %len + br label %loop_body + +loop_body: + %p0 = phi i64* [ %p1, %match_else ], [ %p, %entry ] + %b0 = icmp eq i64* %p0, null + %b1 = icmp eq i64* %q, %p0 + %b2 = or i1 %b0, %b1 + br i1 %b2, label %return, label %match_else + +; CHECK-LABEL: @test_inbounds_recurrence_or_rev_fail1 +; CHECK: icmp eq i64* %p0, null + +match_else: + %i0 = load i64* %p0 + %p1 = getelementptr inbounds i64* %p0, i64 1 + %b3 = icmp ugt i64 %i0, 10 + br i1 %b3, label %return, label %loop_body + +return: + ret i64 0 +} + +define i64 @test_inbounds_recurrence_or_rev_fail2(i64* %p, i64 %len) { +entry: + %q = getelementptr inbounds i64* %p, i64 %len + br label %loop_body + +loop_body: + %p0 = phi i64* [ %p1, %match_else ], [ %p, %entry ] + %b0 = icmp eq i64* %p0, null + %b1 = icmp eq i64* %q, %p0 + %b2 = or i1 %b0, %b1 + br i1 %b2, label %return, label %match_else + +; CHECK-LABEL: @test_inbounds_recurrence_or_rev_fail2 +; CHECK: icmp eq i64* %p0, null + +match_else: + %i0 = load i64* %p0 + %p1 = getelementptr i64* %p0, i64 1 + %b3 = icmp ugt i64 %i0, 10 + br i1 %b3, label %return, label %loop_body + +return: + ret i64 0 +} + +define i64 @test_inbounds_recurrence_and_rev(i64* %p, i64 %len) { +entry: + %q = getelementptr inbounds i64* %p, i64 %len + br label %loop_body + +loop_body: + %p0 = phi i64* [ %p1, %match_else ], [ %p, %entry ] + %b0 = icmp eq i64* %p0, null + %b1 = icmp eq i64* %q, %p0 + %b2 = or i1 %b0, %b1 + br i1 %b2, label %return, label %match_else + +; CHECK-LABEL: @test_inbounds_recurrence_and_rev +; CHECK-NOT: null + +match_else: + %i0 = load i64* %p0 + %p1 = getelementptr inbounds i64* %p0, i64 1 + %b3 = icmp ugt i64 %i0, 10 + br i1 %b3, label %return, label %loop_body + +return: + ret i64 0 +} +