Index: docs/LangRef.rst =================================================================== --- docs/LangRef.rst +++ docs/LangRef.rst @@ -1461,6 +1461,11 @@ trap or generate asynchronous exceptions. Exception handling schemes that are recognized by LLVM to handle asynchronous exceptions, such as SEH, will still provide their implementation defined semantics. +``"null-pointer-is-defined"`` + If ``"null-pointer-is-defined"`` is set to ``"true"``, then ``null`` address + is considered to be a valid address for memory accesses. Any analysis or + optimization should not treat dereferencing a pointer to ``null`` as + ``undefined`` behavior in this function. ``optforfuzzing`` This attribute indicates that this function should be optimized for maximum fuzzing signal. Index: include/llvm/IR/CallSite.h =================================================================== --- include/llvm/IR/CallSite.h +++ include/llvm/IR/CallSite.h @@ -637,7 +637,8 @@ if (hasRetAttr(Attribute::NonNull)) return true; else if (getDereferenceableBytes(AttributeList::ReturnIndex) > 0 && - getType()->getPointerAddressSpace() == 0) + !NullPointerIsDefined(getCaller(), + getType()->getPointerAddressSpace())) return true; return false; Index: include/llvm/IR/Function.h =================================================================== --- include/llvm/IR/Function.h +++ include/llvm/IR/Function.h @@ -781,6 +781,12 @@ /// Returns true if we should emit debug info for profiling. bool isDebugInfoForProfiling() const; + /// Check if null pointer dereferencing is considered undefined behavior for + /// the function. + /// Return value: false => null pointer dereference is undefined. + /// Return value: true => null pointer dereference is not undefined. + bool nullPointerIsDefined() const; + private: void allocHungoffUselist(); template void setHungoffOperand(Constant *C); @@ -793,6 +799,13 @@ void setValueSubclassDataBit(unsigned Bit, bool On); }; +/// Check whether null pointer dereferencing is considered undefined behavior +/// for a given function or an address space. +/// Null pointer access in non-zero address space is not considered undefined. +/// Return value: false => null pointer dereference is undefined. +/// Return value: true => null pointer dereference is not undefined. +bool NullPointerIsDefined(const Function *F, unsigned AS = 0); + template <> struct OperandTraits : public HungoffOperandTraits<3> {}; Index: lib/Analysis/LazyValueInfo.cpp =================================================================== --- lib/Analysis/LazyValueInfo.cpp +++ lib/Analysis/LazyValueInfo.cpp @@ -704,9 +704,11 @@ assert(isa(Val) && "Unknown live-in to the entry block"); // Before giving up, see if we can prove the pointer non-null local to // this particular block. - if (Val->getType()->isPointerTy() && - (isKnownNonZero(Val, DL) || isObjectDereferencedInBlock(Val, BB))) { - PointerType *PTy = cast(Val->getType()); + PointerType *PTy = dyn_cast(Val->getType()); + if (PTy && + (isKnownNonZero(Val, DL) || + (isObjectDereferencedInBlock(Val, BB) && + !NullPointerIsDefined(BB->getParent(), PTy->getAddressSpace())))) { Result = ValueLatticeElement::getNot(ConstantPointerNull::get(PTy)); } else { Result = ValueLatticeElement::getOverdefined(); @@ -739,9 +741,9 @@ << "' - overdefined because of pred (non local).\n"); // Before giving up, see if we can prove the pointer non-null local to // this particular block. - if (Val->getType()->isPointerTy() && - isObjectDereferencedInBlock(Val, BB)) { - PointerType *PTy = cast(Val->getType()); + PointerType *PTy = dyn_cast(Val->getType()); + if (PTy && isObjectDereferencedInBlock(Val, BB) && + !NullPointerIsDefined(BB->getParent(), PTy->getAddressSpace())) { Result = ValueLatticeElement::getNot(ConstantPointerNull::get(PTy)); } Index: lib/Analysis/LoopAccessAnalysis.cpp =================================================================== --- lib/Analysis/LoopAccessAnalysis.cpp +++ lib/Analysis/LoopAccessAnalysis.cpp @@ -1026,8 +1026,9 @@ bool IsNoWrapAddRec = !ShouldCheckWrap || PSE.hasNoOverflow(Ptr, SCEVWrapPredicate::IncrementNUSW) || isNoWrapAddRec(Ptr, AR, PSE, Lp); - bool IsInAddressSpaceZero = PtrTy->getAddressSpace() == 0; - if (!IsNoWrapAddRec && !IsInBoundsGEP && !IsInAddressSpaceZero) { + if (!IsNoWrapAddRec && !IsInBoundsGEP && + NullPointerIsDefined(Lp->getHeader()->getParent(), + PtrTy->getAddressSpace())) { if (Assume) { PSE.setNoOverflow(Ptr, SCEVWrapPredicate::IncrementNUSW); IsNoWrapAddRec = true; @@ -1073,8 +1074,9 @@ // If the SCEV could wrap but we have an inbounds gep with a unit stride we // know we can't "wrap around the address space". In case of address space // zero we know that this won't happen without triggering undefined behavior. - if (!IsNoWrapAddRec && (IsInBoundsGEP || IsInAddressSpaceZero) && - Stride != 1 && Stride != -1) { + if (!IsNoWrapAddRec && Stride != 1 && Stride != -1 && + (IsInBoundsGEP || !NullPointerIsDefined(Lp->getHeader()->getParent(), + PtrTy->getAddressSpace()))) { if (Assume) { // We can avoid this case by adding a run-time check. LLVM_DEBUG(dbgs() << "LAA: Non unit strided pointer which is not either " Index: lib/IR/Function.cpp =================================================================== --- lib/IR/Function.cpp +++ lib/IR/Function.cpp @@ -1409,3 +1409,19 @@ } return None; } + +bool Function::nullPointerIsDefined() const { + return getFnAttribute("null-pointer-is-valid") + .getValueAsString() + .equals("true"); +} + +bool llvm::NullPointerIsDefined(const Function *F, unsigned AS) { + if (F && F->nullPointerIsDefined()) + return true; + + if (AS != 0) + return true; + + return false; +} Index: lib/Transforms/IPO/GlobalOpt.cpp =================================================================== --- lib/Transforms/IPO/GlobalOpt.cpp +++ lib/Transforms/IPO/GlobalOpt.cpp @@ -636,7 +636,12 @@ /// reprocessing them. static bool AllUsesOfValueWillTrapIfNull(const Value *V, SmallPtrSetImpl &PHIs) { - for (const User *U : V->users()) + for (const User *U : V->users()) { + if (const Instruction *I = dyn_cast(U)) { + // If null pointer is considered valid, then all uses are non-trapping. + if (NullPointerIsDefined(I->getFunction())) + return false; + } if (isa(U)) { // Will trap. } else if (const StoreInst *SI = dyn_cast(U)) { @@ -670,7 +675,7 @@ //cerr << "NONTRAPPING USE: " << *U; return false; } - + } return true; } @@ -697,6 +702,10 @@ bool Changed = false; for (auto UI = V->user_begin(), E = V->user_end(); UI != E; ) { Instruction *I = cast(*UI++); + // Uses are non-trapping if null pointer is considered valid. + // Non address-space 0 globals are already pruned by the caller. + if (NullPointerIsDefined(I->getFunction())) + return false; if (LoadInst *LI = dyn_cast(I)) { LI->setOperand(0, NewV); Changed = true; @@ -1584,7 +1593,9 @@ // users of the loaded value (often calls and loads) that would trap if the // value was null. if (GV->getInitializer()->getType()->isPointerTy() && - GV->getInitializer()->isNullValue()) { + GV->getInitializer()->isNullValue() && + !NullPointerIsDefined(nullptr /* F */, + GV->getType()->getAddressSpace())) { if (Constant *SOVC = dyn_cast(StoredOnceVal)) { if (GV->getInitializer()->getType() != SOVC->getType()) SOVC = ConstantExpr::getBitCast(SOVC, GV->getInitializer()->getType()); Index: lib/Transforms/InstCombine/InstCombineCalls.cpp =================================================================== --- lib/Transforms/InstCombine/InstCombineCalls.cpp +++ lib/Transforms/InstCombine/InstCombineCalls.cpp @@ -3891,7 +3891,9 @@ } } - if (isa(Callee) || isa(Callee)) { + if ((isa(Callee) && + !NullPointerIsDefined(CS.getInstruction()->getFunction())) || + isa(Callee)) { // If CS does not return void then replaceAllUsesWith undef. // This allows ValueHandlers and custom metadata to adjust itself. if (!CS.getInstruction()->getType()->isVoidTy()) Index: lib/Transforms/InstCombine/InstCombineLoadStoreAlloca.cpp =================================================================== --- lib/Transforms/InstCombine/InstCombineLoadStoreAlloca.cpp +++ lib/Transforms/InstCombine/InstCombineLoadStoreAlloca.cpp @@ -964,23 +964,26 @@ } static bool canSimplifyNullStoreOrGEP(StoreInst &SI) { - if (SI.getPointerAddressSpace() != 0) + if (NullPointerIsDefined(SI.getFunction(), SI.getPointerAddressSpace())) return false; auto *Ptr = SI.getPointerOperand(); if (GetElementPtrInst *GEPI = dyn_cast(Ptr)) Ptr = GEPI->getOperand(0); - return isa(Ptr); + return (isa(Ptr) && + !NullPointerIsDefined(SI.getFunction(), SI.getPointerAddressSpace())); } static bool canSimplifyNullLoadOrGEP(LoadInst &LI, Value *Op) { if (GetElementPtrInst *GEPI = dyn_cast(Op)) { const Value *GEPI0 = GEPI->getOperand(0); - if (isa(GEPI0) && GEPI->getPointerAddressSpace() == 0) + if (isa(GEPI0) && + !NullPointerIsDefined(LI.getFunction(), GEPI->getPointerAddressSpace())) return true; } if (isa(Op) || - (isa(Op) && LI.getPointerAddressSpace() == 0)) + (isa(Op) && + !NullPointerIsDefined(LI.getFunction(), LI.getPointerAddressSpace()))) return true; return false; } @@ -1076,14 +1079,16 @@ // load (select (cond, null, P)) -> load P if (isa(SI->getOperand(1)) && - LI.getPointerAddressSpace() == 0) { + !NullPointerIsDefined(SI->getFunction(), + LI.getPointerAddressSpace())) { LI.setOperand(0, SI->getOperand(2)); return &LI; } // load (select (cond, P, null)) -> load P if (isa(SI->getOperand(2)) && - LI.getPointerAddressSpace() == 0) { + !NullPointerIsDefined(SI->getFunction(), + LI.getPointerAddressSpace())) { LI.setOperand(0, SI->getOperand(1)); return &LI; } Index: lib/Transforms/Scalar/SCCP.cpp =================================================================== --- lib/Transforms/Scalar/SCCP.cpp +++ lib/Transforms/Scalar/SCCP.cpp @@ -1125,8 +1125,12 @@ Constant *Ptr = PtrVal.getConstant(); // load null is undefined. - if (isa(Ptr) && I.getPointerAddressSpace() == 0) - return; + if (isa(Ptr)) { + if (NullPointerIsDefined(I.getFunction(), I.getPointerAddressSpace())) + return markOverdefined(IV, &I); + else + return; + } // Transform load (constant global) into the value loaded. if (auto *GV = dyn_cast(Ptr)) { Index: lib/Transforms/Utils/Local.cpp =================================================================== --- lib/Transforms/Utils/Local.cpp +++ lib/Transforms/Utils/Local.cpp @@ -1810,7 +1810,9 @@ if (auto *CI = dyn_cast(&I)) { Value *Callee = CI->getCalledValue(); - if (isa(Callee) || isa(Callee)) { + if ((isa(Callee) && + !NullPointerIsDefined(CI->getFunction())) || + isa(Callee)) { changeToUnreachable(CI, /*UseLLVMTrap=*/false, false, DDT); Changed = true; break; @@ -1839,7 +1841,8 @@ if (isa(Ptr) || (isa(Ptr) && - SI->getPointerAddressSpace() == 0)) { + !NullPointerIsDefined(SI->getFunction(), + SI->getPointerAddressSpace()))) { changeToUnreachable(SI, true, false, DDT); Changed = true; break; @@ -1851,7 +1854,9 @@ if (auto *II = dyn_cast(Terminator)) { // Turn invokes that call 'nounwind' functions into ordinary calls. Value *Callee = II->getCalledValue(); - if (isa(Callee) || isa(Callee)) { + if ((isa(Callee) && + !NullPointerIsDefined(BB->getParent())) || + isa(Callee)) { changeToUnreachable(II, true, false, DDT); Changed = true; } else if (II->doesNotThrow() && canSimplifyInvokeNoUnwind(&F)) { Index: lib/Transforms/Utils/SimplifyCFG.cpp =================================================================== --- lib/Transforms/Utils/SimplifyCFG.cpp +++ lib/Transforms/Utils/SimplifyCFG.cpp @@ -5950,17 +5950,20 @@ // Load from null is undefined. if (LoadInst *LI = dyn_cast(Use)) if (!LI->isVolatile()) - return LI->getPointerAddressSpace() == 0; + return !NullPointerIsDefined(LI->getFunction(), + LI->getPointerAddressSpace()); // Store to null is undefined. if (StoreInst *SI = dyn_cast(Use)) if (!SI->isVolatile()) - return SI->getPointerAddressSpace() == 0 && + return (!NullPointerIsDefined(SI->getFunction(), + SI->getPointerAddressSpace())) && SI->getPointerOperand() == I; // A call to null is undefined. if (auto CS = CallSite(Use)) - return CS.getCalledValue() == I; + return !NullPointerIsDefined(CS->getFunction()) && + CS.getCalledValue() == I; } return false; } Index: test/Transforms/CorrelatedValuePropagation/non-null.ll =================================================================== --- test/Transforms/CorrelatedValuePropagation/non-null.ll +++ test/Transforms/CorrelatedValuePropagation/non-null.ll @@ -10,6 +10,16 @@ ret void } +define void @test1_no_null_opt(i8* %ptr) #0 { +; CHECK: test1_no_null_opt + %A = load i8, i8* %ptr + br label %bb +bb: + icmp ne i8* %ptr, null +; CHECK: icmp ne i8* %ptr, null + ret void +} + define void @test2(i8* %ptr) { ; CHECK: test2 store i8 0, i8* %ptr @@ -20,6 +30,16 @@ ret void } +define void @test2_no_null_opt(i8* %ptr) #0 { +; CHECK: test2_no_null_opt + store i8 0, i8* %ptr + br label %bb +bb: + icmp ne i8* %ptr, null +; CHECK: icmp ne i8* %ptr, null + ret void +} + define void @test3() { ; CHECK: test3 %ptr = alloca i8 @@ -30,6 +50,17 @@ ret void } +;; OK to remove icmp here since ptr is coming from alloca. +define void @test3_no_null_opt() #0 { +; CHECK: test3 + %ptr = alloca i8 + br label %bb +bb: + icmp ne i8* %ptr, null +; CHECK-NOT: icmp + ret void +} + declare void @llvm.memcpy.p0i8.p0i8.i32(i8*, i8*, i32, i1) define void @test4(i8* %dest, i8* %src) { ; CHECK: test4 @@ -42,6 +73,18 @@ ret void } +define void @test4_no_null_opt(i8* %dest, i8* %src) #0 { +; CHECK: test4_no_null_opt + call void @llvm.memcpy.p0i8.p0i8.i32(i8* %dest, i8* %src, i32 1, i1 false) + br label %bb +bb: + icmp ne i8* %dest, null + icmp ne i8* %src, null +; CHECK: icmp ne i8* %dest, null +; CHECK: icmp ne i8* %src, null + ret void +} + declare void @llvm.memmove.p0i8.p0i8.i32(i8*, i8*, i32, i1) define void @test5(i8* %dest, i8* %src) { ; CHECK: test5 @@ -54,6 +97,18 @@ ret void } +define void @test5_no_null_opt(i8* %dest, i8* %src) #0 { +; CHECK: test5_no_null_opt + call void @llvm.memmove.p0i8.p0i8.i32(i8* %dest, i8* %src, i32 1, i1 false) + br label %bb +bb: + icmp ne i8* %dest, null + icmp ne i8* %src, null +; CHECK: icmp ne i8* %dest, null +; CHECK: icmp ne i8* %src, null + ret void +} + declare void @llvm.memset.p0i8.i32(i8*, i8, i32, i1) define void @test6(i8* %dest) { ; CHECK: test6 @@ -65,6 +120,16 @@ ret void } +define void @test6_no_null_opt(i8* %dest) #0 { +; CHECK: test6_no_null_opt + call void @llvm.memset.p0i8.i32(i8* %dest, i8 255, i32 1, i1 false) + br label %bb +bb: + icmp ne i8* %dest, null +; CHECK: icmp ne i8* %dest, null + ret void +} + define void @test7(i8* %dest, i8* %src, i32 %len) { ; CHECK: test7 call void @llvm.memcpy.p0i8.p0i8.i32(i8* %dest, i8* %src, i32 %len, i1 false) @@ -161,3 +226,5 @@ ; CHECK: call void @test12_helper(i8* nonnull %merged_arg) ret void } + +attributes #0 = { "null-pointer-is-valid"="true" } Index: test/Transforms/GVN/PRE/2018-06-08-pre-load-dbgloc-no-null-opt.ll =================================================================== --- /dev/null +++ test/Transforms/GVN/PRE/2018-06-08-pre-load-dbgloc-no-null-opt.ll @@ -0,0 +1,82 @@ +; This test checks if debug loc is propagated to load/store created by GVN/Instcombine. +; RUN: opt < %s -gvn -S | FileCheck %s --check-prefixes=ALL +; RUN: opt < %s -gvn -instcombine -S | FileCheck %s --check-prefixes=ALL + +; struct node { +; int *v; +; struct desc *descs; +; }; + +; struct desc { +; struct node *node; +; }; + +; extern int bar(void *v, void* n); + +; int test(struct desc *desc) +; { +; void *v, *n; +; v = !desc ? ((void *)0) : desc->node->v; // Line 15 +; n = &desc->node->descs[0]; // Line 16 +; return bar(v, n); +; } + +; Line 16, Column 13: +; n = &desc->node->descs[0]; +; ^ + +target datalayout = "e-m:e-i8:8:32-i16:16:32-i64:64-i128:128-n32:64-S128" +target triple = "aarch64--linux-gnu" + +%struct.desc = type { %struct.node* } +%struct.node = type { i32*, %struct.desc* } + +define i32 @test_no_null_opt(%struct.desc* readonly %desc) local_unnamed_addr #0 !dbg !4 { +entry: + %tobool = icmp eq %struct.desc* %desc, null + br i1 %tobool, label %cond.end, label %cond.false, !dbg !9 +; ALL: br i1 %tobool, label %entry.cond.end_crit_edge, label %cond.false, !dbg [[LOC_15_6:![0-9]+]] +; ALL: entry.cond.end_crit_edge: +; ALL: load %struct.node*, %struct.node** null, align {{[0-9]+}}, !dbg [[LOC_16_13:![0-9]+]] + +cond.false: + %0 = bitcast %struct.desc* %desc to i8***, !dbg !11 + %1 = load i8**, i8*** %0, align 8, !dbg !11 + %2 = load i8*, i8** %1, align 8 + br label %cond.end, !dbg !9 + +cond.end: +; ALL: phi %struct.node* [ %3, %cond.false ], [ %.pre, %entry.cond.end_crit_edge ] +; ALL: phi i8* [ %2, %cond.false ], [ null, %entry.cond.end_crit_edge ] + + %3 = phi i8* [ %2, %cond.false ], [ null, %entry ], !dbg !9 + %node2 = getelementptr inbounds %struct.desc, %struct.desc* %desc, i64 0, i32 0 + %4 = load %struct.node*, %struct.node** %node2, align 8, !dbg !10 + %descs = getelementptr inbounds %struct.node, %struct.node* %4, i64 0, i32 1 + %5 = bitcast %struct.desc** %descs to i8** + %6 = load i8*, i8** %5, align 8 + %call = tail call i32 @bar(i8* %3, i8* %6) + ret i32 %call +} +attributes #0 = { "null-pointer-is-valid"="true" } + +declare i32 @bar(i8*, i8*) local_unnamed_addr #1 +!llvm.dbg.cu = !{!0} +!llvm.module.flags = !{!2, !3} + +!0 = distinct !DICompileUnit(language: DW_LANG_C99, file: !1, emissionKind: FullDebug) +!1 = !DIFile(filename: "test.c", directory: ".") +!2 = !{i32 2, !"Dwarf Version", i32 4} +!3 = !{i32 2, !"Debug Info Version", i32 3} +!4 = distinct !DISubprogram(name: "test_no_null_opt", scope: !1, file: !1, line: 12, type: !5, isLocal: false, isDefinition: true, scopeLine: 13, flags: DIFlagPrototyped, isOptimized: true, unit: !0, retainedNodes: !8) +!5 = !DISubroutineType(types: !6) +!6 = !{!7} +!7 = !DIBasicType(name: "int", size: 32, encoding: DW_ATE_signed) +!8 = !{} +!9 = !DILocation(line: 15, column: 6, scope: !4) +!10 = !DILocation(line: 16, column: 13, scope: !4) +!11 = !DILocation(line: 15, column: 34, scope: !4) + +;ALL: [[SCOPE:![0-9]+]] = distinct !DISubprogram(name: "test_no_null_opt",{{.*}} +;ALL: [[LOC_15_6]] = !DILocation(line: 15, column: 6, scope: [[SCOPE]]) +;ALL: [[LOC_16_13]] = !DILocation(line: 16, column: 13, scope: [[SCOPE]]) Index: test/Transforms/GlobalOpt/heap-sra-1-no-null-opt.ll =================================================================== --- test/Transforms/GlobalOpt/heap-sra-1-no-null-opt.ll +++ test/Transforms/GlobalOpt/heap-sra-1-no-null-opt.ll @@ -1,12 +1,12 @@ ; RUN: opt < %s -globalopt -S | FileCheck %s target datalayout = "E-p:64:64:64-a0:0:8-f32:32:32-f64:64:64-i1:8:8-i8:8:8-i16:16:16-i32:32:32-i64:32:64-v64:64:64-v128:128:128" - %struct.foo = type { i32, i32 } +%struct.foo = type { i32, i32 } @X = internal global %struct.foo* null -; CHECK: @X.f0 -; CHECK: @X.f1 +; CHECK: @X +; CHECK-NOT: @X.f0 -define void @bar(i64 %Size) nounwind noinline { +define void @bar(i64 %Size) nounwind noinline #0 { entry: %mallocsize = mul i64 %Size, 8 ; [#uses=1] %malloccall = tail call i8* @malloc(i64 %mallocsize) ; [#uses=1] @@ -17,9 +17,9 @@ declare noalias i8* @malloc(i64) -define i32 @baz() nounwind readonly noinline { +define i32 @baz() nounwind readonly noinline #0 { bb1.thread: - %0 = load %struct.foo*, %struct.foo** @X, align 4 + %0 = load %struct.foo*, %struct.foo** @X, align 4 br label %bb1 bb1: ; preds = %bb1, %bb1.thread @@ -27,12 +27,14 @@ %sum.0.reg2mem.0 = phi i32 [ 0, %bb1.thread ], [ %3, %bb1 ] %1 = getelementptr %struct.foo, %struct.foo* %0, i32 %i.0.reg2mem.0, i32 0 %2 = load i32, i32* %1, align 4 - %3 = add i32 %2, %sum.0.reg2mem.0 - %indvar.next = add i32 %i.0.reg2mem.0, 1 - %exitcond = icmp eq i32 %indvar.next, 1200 + %3 = add i32 %2, %sum.0.reg2mem.0 + %indvar.next = add i32 %i.0.reg2mem.0, 1 + %exitcond = icmp eq i32 %indvar.next, 1200 br i1 %exitcond, label %bb2, label %bb1 bb2: ; preds = %bb1 ret i32 %3 } +attributes #0 = { "null-pointer-is-valid"="true" } + Index: test/Transforms/GlobalOpt/heap-sra-1.ll =================================================================== --- test/Transforms/GlobalOpt/heap-sra-1.ll +++ test/Transforms/GlobalOpt/heap-sra-1.ll @@ -36,3 +36,10 @@ ret i32 %3 } +define void @bam(i64 %Size) nounwind noinline #0 { +entry: + %0 = load %struct.foo*, %struct.foo** @X, align 4 + ret void +} + +attributes #0 = { "null-pointer-is-valid"="true" } Index: test/Transforms/GlobalOpt/heap-sra-2-no-null-opt.ll =================================================================== --- test/Transforms/GlobalOpt/heap-sra-2-no-null-opt.ll +++ test/Transforms/GlobalOpt/heap-sra-2-no-null-opt.ll @@ -1,12 +1,12 @@ ; RUN: opt < %s -globalopt -S | FileCheck %s target datalayout = "E-p:64:64:64-a0:0:8-f32:32:32-f64:64:64-i1:8:8-i8:8:8-i16:16:16-i32:32:32-i64:32:64-v64:64:64-v128:128:128" - %struct.foo = type { i32, i32 } +%struct.foo = type { i32, i32 } @X = internal global %struct.foo* null ; <%struct.foo**> [#uses=2] -; CHECK: @X.f0 -; CHECK: @X.f1 +; CHECK: @X +; CHECK-NOT: @X.f0 -define void @bar(i32 %Size) nounwind noinline { +define void @bar(i32 %Size) nounwind noinline #0 { entry: %malloccall = tail call i8* @malloc(i64 8000000) ; [#uses=1] %0 = bitcast i8* %malloccall to [1000000 x %struct.foo]* ; <[1000000 x %struct.foo]*> [#uses=1] @@ -17,7 +17,7 @@ declare noalias i8* @malloc(i64) -define i32 @baz() nounwind readonly noinline { +define i32 @baz() nounwind readonly noinline #0 { bb1.thread: %0 = load %struct.foo*, %struct.foo** @X, align 4 ; <%struct.foo*> [#uses=1] br label %bb1 @@ -36,3 +36,4 @@ ret i32 %3 } +attributes #0 = { "null-pointer-is-valid"="true" } Index: test/Transforms/GlobalOpt/heap-sra-2.ll =================================================================== --- test/Transforms/GlobalOpt/heap-sra-2.ll +++ test/Transforms/GlobalOpt/heap-sra-2.ll @@ -36,3 +36,10 @@ ret i32 %3 } +define void @bam(i64 %Size) nounwind noinline #0 { +entry: + %0 = load %struct.foo*, %struct.foo** @X, align 4 + ret void +} + +attributes #0 = { "null-pointer-is-valid"="true" } Index: test/Transforms/GlobalOpt/heap-sra-3-no-null-opt.ll =================================================================== --- test/Transforms/GlobalOpt/heap-sra-3-no-null-opt.ll +++ test/Transforms/GlobalOpt/heap-sra-3-no-null-opt.ll @@ -1,15 +1,15 @@ ; RUN: opt < %s -globalopt -S | FileCheck %s target datalayout = "E-p:64:64:64-a0:0:8-f32:32:32-f64:64:64-i1:8:8-i8:8:8-i16:16:16-i32:32:32-i64:32:64-v64:64:64-v128:128:128" - %struct.foo = type { i32, i32 } +%struct.foo = type { i32, i32 } @X = internal global %struct.foo* null -; CHECK: @X.f0 -; CHECK: @X.f1 +; CHECK: @X +; CHECK-NOT: @X.f0 -define void @bar(i64 %Size) nounwind noinline { +define void @bar(i64 %Size) nounwind noinline #0 { entry: %mallocsize = mul i64 8, %Size ; [#uses=1] -; CHECK: mul i64 %Size, 4 +; CHECK: mul i64 8, %Size %malloccall = tail call i8* @malloc(i64 %mallocsize) ; [#uses=1] %.sub = bitcast i8* %malloccall to %struct.foo* ; <%struct.foo*> [#uses=1] store %struct.foo* %.sub, %struct.foo** @X, align 4 @@ -18,9 +18,10 @@ declare noalias i8* @malloc(i64) -define i32 @baz() nounwind readonly noinline { +define i32 @baz() nounwind readonly noinline #0 { bb1.thread: - %0 = load %struct.foo*, %struct.foo** @X, align 4 +; CHECK: load %struct.foo*, %struct.foo** @X, align 4 + %0 = load %struct.foo*, %struct.foo** @X, align 4 br label %bb1 bb1: ; preds = %bb1, %bb1.thread @@ -28,12 +29,13 @@ %sum.0.reg2mem.0 = phi i32 [ 0, %bb1.thread ], [ %3, %bb1 ] %1 = getelementptr %struct.foo, %struct.foo* %0, i32 %i.0.reg2mem.0, i32 0 %2 = load i32, i32* %1, align 4 - %3 = add i32 %2, %sum.0.reg2mem.0 - %indvar.next = add i32 %i.0.reg2mem.0, 1 - %exitcond = icmp eq i32 %indvar.next, 1200 + %3 = add i32 %2, %sum.0.reg2mem.0 + %indvar.next = add i32 %i.0.reg2mem.0, 1 + %exitcond = icmp eq i32 %indvar.next, 1200 br i1 %exitcond, label %bb2, label %bb1 bb2: ; preds = %bb1 ret i32 %3 } +attributes #0 = { "null-pointer-is-valid"="true" } Index: test/Transforms/GlobalOpt/heap-sra-3.ll =================================================================== --- test/Transforms/GlobalOpt/heap-sra-3.ll +++ test/Transforms/GlobalOpt/heap-sra-3.ll @@ -37,3 +37,10 @@ ret i32 %3 } +define void @bam(i64 %Size) nounwind noinline #0 { +entry: + %0 = load %struct.foo*, %struct.foo** @X, align 4 + ret void +} + +attributes #0 = { "null-pointer-is-valid"="true" } Index: test/Transforms/GlobalOpt/heap-sra-4-no-null-opt.ll =================================================================== --- test/Transforms/GlobalOpt/heap-sra-4-no-null-opt.ll +++ test/Transforms/GlobalOpt/heap-sra-4-no-null-opt.ll @@ -1,16 +1,17 @@ ; RUN: opt < %s -globalopt -S | FileCheck %s target datalayout = "E-p:64:64:64-a0:0:8-f32:32:32-f64:64:64-i1:8:8-i8:8:8-i16:16:16-i32:32:32-i64:32:64-v64:64:64-v128:128:128" - %struct.foo = type { i32, i32 } +%struct.foo = type { i32, i32 } + @X = internal global %struct.foo* null -; CHECK: @X.f0 -; CHECK: @X.f1 +; CHECK: @X +; CHECK-NOT: @X.f0 -define void @bar(i64 %Size) nounwind noinline { +define void @bar(i64 %Size) nounwind noinline #0 { entry: %mallocsize = shl i64 %Size, 3 ; [#uses=1] %malloccall = tail call i8* @malloc(i64 %mallocsize) ; [#uses=1] -; CHECK: mul i64 %Size, 4 +; CHECK: shl i64 %Size, 3 %.sub = bitcast i8* %malloccall to %struct.foo* ; <%struct.foo*> [#uses=1] store %struct.foo* %.sub, %struct.foo** @X, align 4 ret void @@ -18,9 +19,11 @@ declare noalias i8* @malloc(i64) -define i32 @baz() nounwind readonly noinline { +define i32 @baz() nounwind readonly noinline #0 { +; CHECK-LABEL: @baz( bb1.thread: - %0 = load %struct.foo*, %struct.foo** @X, align 4 +; CHECK: load %struct.foo*, %struct.foo** @X, align 4 + %0 = load %struct.foo*, %struct.foo** @X, align 4 br label %bb1 bb1: ; preds = %bb1, %bb1.thread @@ -28,12 +31,14 @@ %sum.0.reg2mem.0 = phi i32 [ 0, %bb1.thread ], [ %3, %bb1 ] %1 = getelementptr %struct.foo, %struct.foo* %0, i32 %i.0.reg2mem.0, i32 0 %2 = load i32, i32* %1, align 4 - %3 = add i32 %2, %sum.0.reg2mem.0 - %indvar.next = add i32 %i.0.reg2mem.0, 1 - %exitcond = icmp eq i32 %indvar.next, 1200 +; CHECK: load i32, i32* %1, align 4 + %3 = add i32 %2, %sum.0.reg2mem.0 + %indvar.next = add i32 %i.0.reg2mem.0, 1 + %exitcond = icmp eq i32 %indvar.next, 1200 br i1 %exitcond, label %bb2, label %bb1 bb2: ; preds = %bb1 ret i32 %3 } +attributes #0 = { "null-pointer-is-valid"="true" } Index: test/Transforms/GlobalOpt/heap-sra-4.ll =================================================================== --- test/Transforms/GlobalOpt/heap-sra-4.ll +++ test/Transforms/GlobalOpt/heap-sra-4.ll @@ -37,3 +37,11 @@ ret i32 %3 } +define void @bam(i64 %Size) nounwind noinline #0 { +entry: + %0 = load %struct.foo*, %struct.foo** @X, align 4 + ret void +} + +attributes #0 = { "null-pointer-is-valid"="true" } + Index: test/Transforms/GlobalOpt/heap-sra-phi-no-null-opt.ll =================================================================== --- test/Transforms/GlobalOpt/heap-sra-phi-no-null-opt.ll +++ test/Transforms/GlobalOpt/heap-sra-phi-no-null-opt.ll @@ -1,12 +1,14 @@ ; RUN: opt < %s -globalopt -S | FileCheck %s -; CHECK: tmp.f1 = phi i32* -; CHECK: tmp.f0 = phi i32* target datalayout = "E-p:64:64:64-a0:0:8-f32:32:32-f64:64:64-i1:8:8-i8:8:8-i16:16:16-i32:32:32-i64:32:64-v64:64:64-v128:128:128" - %struct.foo = type { i32, i32 } +%struct.foo = type { i32, i32 } + @X = internal global %struct.foo* null ; <%struct.foo**> [#uses=2] +; CHECK: @X +; CHECK-NOT: @X.f0 -define void @bar(i32 %Size) nounwind noinline { +define void @bar(i32 %Size) nounwind noinline #0 { +; CHECK-LABEL: @bar( entry: %malloccall = tail call i8* @malloc(i64 8000000) ; [#uses=1] %tmp = bitcast i8* %malloccall to [1000000 x %struct.foo]* ; <[1000000 x %struct.foo]*> [#uses=1] @@ -17,24 +19,30 @@ declare noalias i8* @malloc(i64) -define i32 @baz() nounwind readonly noinline { +define i32 @baz() nounwind readonly noinline #0 { +; CHECK-LABEL: @baz( bb1.thread: %tmpLD1 = load %struct.foo*, %struct.foo** @X, align 4 ; <%struct.foo*> [#uses=1] +; CHECK: load %struct.foo*, %struct.foo** @X, align 4 br label %bb1 bb1: ; preds = %bb1, %bb1.thread %tmp = phi %struct.foo* [%tmpLD1, %bb1.thread ], [ %tmpLD2, %bb1 ] ; [#uses=2] +; CHECK: %tmp = phi %struct.foo* [ %tmpLD1, %bb1.thread ], [ %tmpLD2, %bb1 ] %i.0.reg2mem.0 = phi i32 [ 0, %bb1.thread ], [ %indvar.next, %bb1 ] ; [#uses=2] %sum.0.reg2mem.0 = phi i32 [ 0, %bb1.thread ], [ %tmp3, %bb1 ] ; [#uses=1] %tmp1 = getelementptr %struct.foo, %struct.foo* %tmp, i32 %i.0.reg2mem.0, i32 0 ; [#uses=1] %tmp2 = load i32, i32* %tmp1, align 4 ; [#uses=1] +; CHECK: load i32, i32* %tmp1, align 4 %tmp6 = add i32 %tmp2, %sum.0.reg2mem.0 ; [#uses=2] %tmp4 = getelementptr %struct.foo, %struct.foo* %tmp, i32 %i.0.reg2mem.0, i32 1 ; [#uses=1] %tmp5 = load i32 , i32 * %tmp4 +; CHECK: load i32, i32* %tmp4 %tmp3 = add i32 %tmp5, %tmp6 %indvar.next = add i32 %i.0.reg2mem.0, 1 ; [#uses=2] - + %tmpLD2 = load %struct.foo*, %struct.foo** @X, align 4 ; <%struct.foo*> [#uses=1] +; CHECK: load %struct.foo*, %struct.foo** @X, align 4 %exitcond = icmp eq i32 %indvar.next, 1200 ; [#uses=1] br i1 %exitcond, label %bb2, label %bb1 @@ -42,3 +50,5 @@ bb2: ; preds = %bb1 ret i32 %tmp3 } + +attributes #0 = { "null-pointer-is-valid"="true" } Index: test/Transforms/GlobalOpt/heap-sra-phi.ll =================================================================== --- test/Transforms/GlobalOpt/heap-sra-phi.ll +++ test/Transforms/GlobalOpt/heap-sra-phi.ll @@ -42,3 +42,11 @@ bb2: ; preds = %bb1 ret i32 %tmp3 } + +define void @bam(i64 %Size) nounwind noinline #0 { +entry: + %0 = load %struct.foo*, %struct.foo** @X, align 4 + ret void +} + +attributes #0 = { "null-pointer-is-valid"="true" } Index: test/Transforms/GlobalOpt/malloc-promote-1-no-null-opt.ll =================================================================== --- test/Transforms/GlobalOpt/malloc-promote-1-no-null-opt.ll +++ test/Transforms/GlobalOpt/malloc-promote-1-no-null-opt.ll @@ -2,9 +2,12 @@ target datalayout = "E-p:64:64:64-a0:0:8-f32:32:32-f64:64:64-i1:8:8-i8:8:8-i16:16:16-i32:32:32-i64:32:64-v64:64:64-v128:128:128" @G = internal global i32* null ; [#uses=3] -; CHECK-NOT: global +; CHECK: global -define void @init() { +define void @init() #0 { +; CHECK-LABEL: @init( +; CHECK: store +; CHECK: load %malloccall = tail call i8* @malloc(i64 4) ; [#uses=1] %P = bitcast i8* %malloccall to i32* ; [#uses=1] store i32* %P, i32** @G @@ -15,10 +18,14 @@ declare noalias i8* @malloc(i64) -define i32 @get() { +define i32 @get() #0 { +; CHECK-LABEL: @get( +; CHECK: load i32*, i32** @G +; CHECK-NEXT: load i32, i32* %GV %GV = load i32*, i32** @G ; [#uses=1] %V = load i32, i32* %GV ; [#uses=1] ret i32 %V -; CHECK: ret i32 0 +; CHECK: ret i32 %V } +attributes #0 = { "null-pointer-is-valid"="true" } Index: test/Transforms/GlobalOpt/malloc-promote-1.ll =================================================================== --- test/Transforms/GlobalOpt/malloc-promote-1.ll +++ test/Transforms/GlobalOpt/malloc-promote-1.ll @@ -1,7 +1,7 @@ ; RUN: opt < %s -globalopt -S | FileCheck %s target datalayout = "E-p:64:64:64-a0:0:8-f32:32:32-f64:64:64-i1:8:8-i8:8:8-i16:16:16-i32:32:32-i64:32:64-v64:64:64-v128:128:128" -@G = internal global i32* null ; [#uses=3] +@G = internal global i32* null ; [#uses=4] ; CHECK-NOT: global define void @init() { @@ -22,3 +22,11 @@ ; CHECK: ret i32 0 } +define void @foo(i64 %Size) nounwind noinline #0 { +entry: + %0 = load i32*, i32** @G, align 4 + ret void +} + +attributes #0 = { "null-pointer-is-valid"="true" } + Index: test/Transforms/GlobalOpt/malloc-promote-2-no-null-opt.ll =================================================================== --- test/Transforms/GlobalOpt/malloc-promote-2-no-null-opt.ll +++ test/Transforms/GlobalOpt/malloc-promote-2-no-null-opt.ll @@ -3,10 +3,14 @@ @G = internal global i32* null -define void @t() { +define void @t() #0 { ; CHECK: @t() -; CHECK-NOT: call i8* @malloc -; CHECK-NEXT: ret void +; CHECK: call i8* @malloc +; CHECK: bitcast +; CHECK: store +; CHECK: load +; CHECK: getelementptr +; CHECK: store %malloccall = tail call i8* @malloc(i64 mul (i64 100, i64 4)) %P = bitcast i8* %malloccall to i32* store i32* %P, i32** @G @@ -17,3 +21,4 @@ } declare noalias i8* @malloc(i64) +attributes #0 = { "null-pointer-is-valid"="true" } Index: test/Transforms/GlobalOpt/malloc-promote-2.ll =================================================================== --- test/Transforms/GlobalOpt/malloc-promote-2.ll +++ test/Transforms/GlobalOpt/malloc-promote-2.ll @@ -17,3 +17,11 @@ } declare noalias i8* @malloc(i64) + +define void @foo(i64 %Size) nounwind noinline #0 { +entry: + %0 = load i32*, i32** @G, align 4 + ret void +} + +attributes #0 = { "null-pointer-is-valid"="true" } Index: test/Transforms/GlobalOpt/storepointer-compare-no-null-opt.ll =================================================================== --- /dev/null +++ test/Transforms/GlobalOpt/storepointer-compare-no-null-opt.ll @@ -0,0 +1,40 @@ +; RUN: opt < %s -globalopt -S | FileCheck %s +; CHECK: global + +@G = internal global void ()* null ; [#uses=2] + +define internal void @Actual() { +; CHECK-LABEL: Actual( + ret void +} + +define void @init() { +; CHECK-LABEL: init( +; CHECK: store void ()* @Actual, void ()** @G + store void ()* @Actual, void ()** @G + ret void +} + +define void @doit() #0 { +; CHECK-LABEL: doit( + %FP = load void ()*, void ()** @G ; [#uses=2] +; CHECK: %FP = load void ()*, void ()** @G + %CC = icmp eq void ()* %FP, null ; [#uses=1] +; CHECK: %CC = icmp eq void ()* %FP, null + br i1 %CC, label %isNull, label %DoCall +; CHECK: br i1 %CC, label %isNull, label %DoCall + +DoCall: ; preds = %0 +; CHECK: DoCall: +; CHECK: call void %FP() +; CHECK: ret void + call void %FP( ) + ret void + +isNull: ; preds = %0 +; CHECK: isNull: +; CHECK: ret void + ret void +} + +attributes #0 = { "null-pointer-is-valid"="true" } Index: test/Transforms/GlobalOpt/storepointer-no-null-opt.ll =================================================================== --- /dev/null +++ test/Transforms/GlobalOpt/storepointer-no-null-opt.ll @@ -0,0 +1,27 @@ +; RUN: opt < %s -globalopt -S | FileCheck %s + +@G = internal global void ()* null ; [#uses=2] +; CHECK: global + +define internal void @Actual() { +; CHECK-LABEL: Actual( + ret void +} + +define void @init() { +; CHECK-LABEL: init( +; CHECK: store void ()* @Actual, void ()** @G + store void ()* @Actual, void ()** @G + ret void +} + +define void @doit() #0 { +; CHECK-LABEL: doit( +; CHECK: %FP = load void ()*, void ()** @G +; CHECK: call void %FP() + %FP = load void ()*, void ()** @G ; [#uses=1] + call void %FP( ) + ret void +} + +attributes #0 = { "null-pointer-is-valid"="true" } Index: test/Transforms/IPConstantProp/PR26044.ll =================================================================== --- test/Transforms/IPConstantProp/PR26044.ll +++ test/Transforms/IPConstantProp/PR26044.ll @@ -23,9 +23,40 @@ ret i32 %cond } +define void @fn_no_null_opt() #0 { +entry: + br label %if.end + +for.cond1: ; preds = %if.end, %for.end + br i1 undef, label %if.end, label %if.end + +if.end: ; preds = %lbl, %for.cond1 + %e.2 = phi i32* [ undef, %entry ], [ null, %for.cond1 ], [ null, %for.cond1 ] + %0 = load i32, i32* %e.2, align 4 + %call = call i32 @fn0(i32 %0) + br label %for.cond1 +} + +define internal i32 @fn0(i32 %p1) { +entry: + %tobool = icmp ne i32 %p1, 0 + %cond = select i1 %tobool, i32 %p1, i32 %p1 + ret i32 %cond +} + +attributes #0 = { "null-pointer-is-valid"="true" } + ; CHECK-LABEL: define void @fn2( ; CHECK: call i32 @fn1(i32 undef) ; CHECK-LABEL: define internal i32 @fn1( ; CHECK:%[[COND:.*]] = select i1 undef, i32 undef, i32 undef ; CHECK: ret i32 %[[COND]] + +; CHECK-LABEL: define void @fn_no_null_opt( +; CHECK: call i32 @fn0(i32 %0) + +; CHECK-LABEL: define internal i32 @fn0( +; CHECK:%[[TOBOOL:.*]] = icmp ne i32 %p1, 0 +; CHECK:%[[COND:.*]] = select i1 %[[TOBOOL]], i32 %p1, i32 %p1 +; CHECK: ret i32 %[[COND]] Index: test/Transforms/InstCombine/atomic.ll =================================================================== --- test/Transforms/InstCombine/atomic.ll +++ test/Transforms/InstCombine/atomic.ll @@ -102,6 +102,13 @@ ret i32 %x } +define i32 @test9_no_null_opt() #0 { +; CHECK-LABEL: define i32 @test9_no_null_opt( +; CHECK: load atomic i32, i32* null unordered + %x = load atomic i32, i32* null unordered, align 4 + ret i32 %x +} + ; FIXME: Could also fold define i32 @test10() { ; CHECK-LABEL: define i32 @test10( @@ -110,6 +117,13 @@ ret i32 %x } +define i32 @test10_no_null_opt() #0 { +; CHECK-LABEL: define i32 @test10_no_null_opt( +; CHECK: load atomic i32, i32* null monotonic + %x = load atomic i32, i32* null monotonic, align 4 + ret i32 %x +} + ; Would this be legal to fold? Probably? define i32 @test11() { ; CHECK-LABEL: define i32 @test11( @@ -118,6 +132,13 @@ ret i32 %x } +define i32 @test11_no_null_opt() #0 { +; CHECK-LABEL: define i32 @test11_no_null_opt( +; CHECK: load atomic i32, i32* null seq_cst + %x = load atomic i32, i32* null seq_cst, align 4 + ret i32 %x +} + ; An unordered access to null is still unreachable. There's no ; ordering imposed. define i32 @test12() { @@ -127,6 +148,13 @@ ret i32 0 } +define i32 @test12_no_null_opt() #0 { +; CHECK-LABEL: define i32 @test12_no_null_opt( +; CHECK: store atomic i32 0, i32* null unordered + store atomic i32 0, i32* null unordered, align 4 + ret i32 0 +} + ; FIXME: Could also fold define i32 @test13() { ; CHECK-LABEL: define i32 @test13( @@ -135,6 +163,13 @@ ret i32 0 } +define i32 @test13_no_null_opt() #0 { +; CHECK-LABEL: define i32 @test13_no_null_opt( +; CHECK: store atomic i32 0, i32* null monotonic + store atomic i32 0, i32* null monotonic, align 4 + ret i32 0 +} + ; Would this be legal to fold? Probably? define i32 @test14() { ; CHECK-LABEL: define i32 @test14( @@ -143,6 +178,13 @@ ret i32 0 } +define i32 @test14_no_null_opt() #0 { +; CHECK-LABEL: define i32 @test14_no_null_opt( +; CHECK: store atomic i32 0, i32* null seq_cst + store atomic i32 0, i32* null seq_cst, align 4 + ret i32 0 +} + @a = external global i32 @b = external global i32 @@ -287,3 +329,5 @@ store atomic i64 %1, i64* %2 unordered, align 8 ret void } + +attributes #0 = { "null-pointer-is-valid"="true" } Index: test/Transforms/InstCombine/invoke.ll =================================================================== --- test/Transforms/InstCombine/invoke.ll +++ test/Transforms/InstCombine/invoke.ll @@ -47,6 +47,27 @@ unreachable } +; CHECK-LABEL: @f2_no_null_opt( +define i64 @f2_no_null_opt() nounwind uwtable ssp #0 personality i8* bitcast (i32 (...)* @__gxx_personality_v0 to i8*) { +entry: +; CHECK: invoke noalias i8* null() + %call = invoke noalias i8* null() + to label %invoke.cont unwind label %lpad + +invoke.cont: +; CHECK: call i64 @llvm.objectsize.i64.p0i8(i8* %call, i1 false, i1 false) + %0 = tail call i64 @llvm.objectsize.i64(i8* %call, i1 false) + ret i64 %0 + +lpad: + %1 = landingpad { i8*, i32 } + filter [0 x i8*] zeroinitializer + %2 = extractvalue { i8*, i32 } %1, 0 + tail call void @__cxa_call_unexpected(i8* %2) noreturn nounwind + unreachable +} +attributes #0 = { "null-pointer-is-valid"="true" } + ; CHECK-LABEL: @f3( define void @f3() nounwind uwtable ssp personality i8* bitcast (i32 (...)* @__gxx_personality_v0 to i8*) { ; CHECK: invoke void @llvm.donothing() Index: test/Transforms/InstCombine/load.ll =================================================================== --- test/Transforms/InstCombine/load.ll +++ test/Transforms/InstCombine/load.ll @@ -60,6 +60,16 @@ ret i32 %R } +; CHECK-LABEL: @test7_no_null_opt( +; CHECK: %V = getelementptr i32, i32* null +; CHECK: %R = load i32, i32* %V +define i32 @test7_no_null_opt(i32 %X) #0 { + %V = getelementptr i32, i32* null, i32 %X ; [#uses=1] + %R = load i32, i32* %V ; [#uses=1] + ret i32 %R +} +attributes #0 = { "null-pointer-is-valid"="true" } + ; CHECK-LABEL: @test8( ; CHECK-NOT: load define i32 @test8(i32* %P) { Index: test/Transforms/InstCombine/select.ll =================================================================== --- test/Transforms/InstCombine/select.ll +++ test/Transforms/InstCombine/select.ll @@ -385,6 +385,31 @@ ret i32 %V } +;; It may be legal to load from a null address with null pointer valid attribute. +define i32 @test16_no_null_opt(i1 %C, i32* %P) #0 { +; CHECK-LABEL: @test16_no_null_opt( +; CHECK-NEXT: [[P2:%.*]] = select i1 [[C:%.*]], i32* [[P:%.*]], i32* null +; CHECK-NEXT: [[V:%.*]] = load i32, i32* [[P2]], align 4 +; CHECK-NEXT: ret i32 [[V]] +; + %P2 = select i1 %C, i32* %P, i32* null + %V = load i32, i32* %P2 + ret i32 %V +} + +define i32 @test16_no_null_opt_2(i1 %C, i32* %P) #0 { +; CHECK-LABEL: @test16_no_null_opt_2( +; CHECK-NEXT: [[P2:%.*]] = select i1 [[C:%.*]], i32* null, i32* [[P:%.*]] +; CHECK-NEXT: [[V:%.*]] = load i32, i32* [[P2]], align 4 +; CHECK-NEXT: ret i32 [[V]] +; + %P2 = select i1 %C, i32* null, i32* %P + %V = load i32, i32* %P2 + ret i32 %V +} + +attributes #0 = { "null-pointer-is-valid"="true" } + define i1 @test17(i32* %X, i1 %C) { ; CHECK-LABEL: @test17( ; CHECK-NEXT: [[RV1:%.*]] = icmp eq i32* [[X:%.*]], null Index: test/Transforms/InstCombine/store.ll =================================================================== --- test/Transforms/InstCombine/store.ll +++ test/Transforms/InstCombine/store.ll @@ -28,6 +28,17 @@ ret void } +define void @store_at_gep_off_no_null_opt(i64 %offset) #0 { + %ptr = getelementptr i32, i32 *null, i64 %offset + store i32 24, i32* %ptr + ret void +; CHECK-LABEL: @store_at_gep_off_no_null_opt(i64 %offset) +; CHECK-NEXT: %ptr = getelementptr i32, i32* null, i64 %offset +; CHECK-NEXT: store i32 24, i32* %ptr +} + +attributes #0 = { "null-pointer-is-valid"="true" } + ;; Simple sinking tests ; "if then else" Index: test/Transforms/SimplifyCFG/UnreachableEliminate.ll =================================================================== --- test/Transforms/SimplifyCFG/UnreachableEliminate.ll +++ test/Transforms/SimplifyCFG/UnreachableEliminate.ll @@ -78,6 +78,28 @@ ret void } +define void @test5_no_null_opt(i1 %cond, i8* %ptr) #0 { + +; CHECK-LABEL: test5_no_null_opt +; CHECK: entry: +; CHECK: %[[SEL:.*]] = select i1 %cond, i8* null, i8* %ptr +; CHECK: store i8 2, i8* %[[SEL]] + +entry: + br i1 %cond, label %bb1, label %bb3 + +bb3: + br label %bb2 + +bb1: + br label %bb2 + +bb2: + %ptr.2 = phi i8* [ %ptr, %bb3 ], [ null, %bb1 ] + store i8 2, i8* %ptr.2, align 8 + ret void +} + ; CHECK-LABEL: test6 ; CHECK: entry: ; CHECK-NOT: select @@ -97,6 +119,25 @@ ret void } +; CHECK-LABEL: test6_no_null_opt +; CHECK: entry: +; CHECK: %[[SEL:.*]] = select i1 %cond, i8* null, i8* %ptr +; CHECK: store i8 2, i8* %[[SEL]] + +define void @test6_no_null_opt(i1 %cond, i8* %ptr) #0 { +entry: + br i1 %cond, label %bb1, label %bb2 + +bb1: + br label %bb2 + +bb2: + %ptr.2 = phi i8* [ %ptr, %entry ], [ null, %bb1 ] + store i8 2, i8* %ptr.2, align 8 + ret void +} + + define i32 @test7(i1 %X) { entry: br i1 %X, label %if, label %else @@ -127,3 +168,21 @@ } ; CHECK-LABEL: define void @test8( ; CHECK: call void %Y( + +define void @test8_no_null_opt(i1 %X, void ()* %Y) #0 { +entry: + br i1 %X, label %if, label %else + +if: + br label %else + +else: + %phi = phi void ()* [ %Y, %entry ], [ null, %if ] + call void %phi() + ret void +} +attributes #0 = { "null-pointer-is-valid"="true" } + +; CHECK-LABEL: define void @test8_no_null_opt( +; CHECK: %[[SEL:.*]] = select i1 %X, void ()* null, void ()* %Y +; CHECK: call void %[[SEL]] Index: test/Transforms/SimplifyCFG/invoke.ll =================================================================== --- test/Transforms/SimplifyCFG/invoke.ll +++ test/Transforms/SimplifyCFG/invoke.ll @@ -47,6 +47,27 @@ unreachable } +; CHECK-LABEL: @f2_no_null_opt( +define i8* @f2_no_null_opt() nounwind uwtable ssp #0 personality i8* bitcast (i32 (...)* @__gxx_personality_v0 to i8*) { +entry: +; CHECK: invoke noalias i8* null() + %call = invoke noalias i8* null() + to label %invoke.cont unwind label %lpad + +; CHECK: invoke.cont: +; CHECK: ret i8* %call +invoke.cont: + ret i8* %call + +lpad: + %0 = landingpad { i8*, i32 } + filter [0 x i8*] zeroinitializer + %1 = extractvalue { i8*, i32 } %0, 0 + tail call void @__cxa_call_unexpected(i8* %1) noreturn nounwind +; CHECK: unreachable + unreachable +} + ; CHECK-LABEL: @f3( define i32 @f3() nounwind uwtable ssp personality i8* bitcast (i32 (...)* @__gxx_personality_v0 to i8*) { ; CHECK-NEXT: entry @@ -137,3 +158,5 @@ cleanup ret void } + +attributes #0 = { "null-pointer-is-valid"="true" } Index: test/Transforms/SimplifyCFG/phi-undef-loadstore.ll =================================================================== --- test/Transforms/SimplifyCFG/phi-undef-loadstore.ll +++ test/Transforms/SimplifyCFG/phi-undef-loadstore.ll @@ -31,6 +31,37 @@ ; CHECK: phi i32* [ %a, %if.then ], [ %c, %if.else ] } +define i32 @test1_no_null_opt(i32* %a, i32 %b, i32* %c, i32 %d) nounwind #0 { +entry: + %tobool = icmp eq i32 %b, 0 + br i1 %tobool, label %if.else, label %if.then + +if.then: ; preds = %entry + tail call void @bar() nounwind + br label %if.end7 + +if.else: ; preds = %entry + %tobool3 = icmp eq i32 %d, 0 + br i1 %tobool3, label %if.end7, label %if.then4 + +if.then4: ; preds = %if.else + tail call void @bar() nounwind + br label %if.end7 + +if.end7: ; preds = %if.else, %if.then4, %if.then + %x.0 = phi i32* [ %a, %if.then ], [ %c, %if.then4 ], [ null, %if.else ] + %tmp9 = load i32, i32* %x.0 + ret i32 %tmp9 + +; CHECK-LABEL: @test1_no_null_opt( +; CHECK: if.then: +; CHECK: if.else: +; CHECK: if.then4: +; CHECK: br label %if.end7 +; CHECK: if.end7: +; CHECK-NEXT: phi i32* [ %a, %if.then ], [ %c, %if.then4 ], [ null, %if.else ] +} + define i32 @test2(i32* %a, i32 %b, i32* %c, i32 %d) nounwind { entry: %tobool = icmp eq i32 %b, 0 @@ -59,6 +90,35 @@ ; CHECK-NOT: phi } +define i32 @test2_no_null_opt(i32* %a, i32 %b, i32* %c, i32 %d) nounwind #0 { +entry: + %tobool = icmp eq i32 %b, 0 + br i1 %tobool, label %if.else, label %if.then + +if.then: ; preds = %entry + tail call void @bar() nounwind + br label %if.end7 + +if.else: ; preds = %entry + %tobool3 = icmp eq i32 %d, 0 + br i1 %tobool3, label %if.end7, label %if.then4 + +if.then4: ; preds = %if.else + tail call void @bar() nounwind + br label %if.end7 + +if.end7: ; preds = %if.else, %if.then4, %if.then + %x.0 = phi i32* [ %a, %if.then ], [ null, %if.then4 ], [ null, %if.else ] + %tmp9 = load i32, i32* %x.0 + ret i32 %tmp9 +; CHECK-LABEL: @test2_no_null_opt( +; CHECK: if.then: +; CHECK: if.else: +; CHECK: if.then4: +; CHECK: if.end7: +; CHECK-NEXT: phi i32* [ %a, %if.then ], [ null, %if.then4 ], [ null, %if.else ] +} + define i32 @test3(i32* %a, i32 %b, i32* %c, i32 %d) nounwind { entry: %tobool = icmp eq i32 %b, 0 @@ -86,6 +146,36 @@ ; CHECK: phi i32* [ %a, %if.then ], [ null, %if.then4 ], [ null, %if.else ] } +define i32 @test3_no_null_opt(i32* %a, i32 %b, i32* %c, i32 %d) nounwind #0 { +entry: + %tobool = icmp eq i32 %b, 0 + br i1 %tobool, label %if.else, label %if.then + +if.then: ; preds = %entry + tail call void @bar() nounwind + br label %if.end7 + +if.else: ; preds = %entry + %tobool3 = icmp eq i32 %d, 0 + br i1 %tobool3, label %if.end7, label %if.then4 + +if.then4: ; preds = %if.else + tail call void @bar() nounwind + br label %if.end7 + +if.end7: ; preds = %if.else, %if.then4, %if.then + %x.0 = phi i32* [ %a, %if.then ], [ null, %if.then4 ], [ null, %if.else ] + tail call void @bar() nounwind + %tmp9 = load i32, i32* %x.0 + ret i32 %tmp9 +; CHECK-LABEL: @test3_no_null_opt( +; CHECK: if.then: +; CHECK: if.else: +; CHECK: if.then4: +; CHECK: if.end7: +; CHECK-NEXT: phi i32* [ %a, %if.then ], [ null, %if.then4 ], [ null, %if.else ] +} + define i32 @test4(i32* %a, i32 %b, i32* %c, i32 %d) nounwind { entry: %tobool = icmp eq i32 %b, 0 @@ -113,3 +203,37 @@ ; CHECK-LABEL: @test4( ; CHECK-NOT: phi } + +define i32 @test4_no_null_opt(i32* %a, i32 %b, i32* %c, i32 %d) nounwind #0 { +entry: + %tobool = icmp eq i32 %b, 0 + br i1 %tobool, label %if.else, label %if.then + +if.then: ; preds = %entry + tail call void @bar() nounwind + br label %if.end7 + +if.else: ; preds = %entry + %tobool3 = icmp eq i32 %d, 0 + br i1 %tobool3, label %if.end7, label %if.then4 + +if.then4: ; preds = %if.else + tail call void @bar() nounwind + br label %if.end7 + +if.end7: ; preds = %if.else, %if.then4, %if.then + %x.0 = phi i32* [ %a, %if.then ], [ null, %if.then4 ], [ null, %if.else ] + %gep = getelementptr i32, i32* %x.0, i32 10 + %tmp9 = load i32, i32* %gep + %tmp10 = or i32 %tmp9, 1 + store i32 %tmp10, i32* %gep + ret i32 %tmp9 +; CHECK-LABEL: @test4_no_null_opt( +; CHECK: if.then: +; CHECK: if.else: +; CHECK: if.then4: +; CHECK: if.end7: +; CHECK-NEXT: phi i32* [ %a, %if.then ], [ null, %if.then4 ], [ null, %if.else ] +} + +attributes #0 = { "null-pointer-is-valid"="true" } Index: test/Transforms/SimplifyCFG/trap-no-null-opt-debugloc.ll =================================================================== --- /dev/null +++ test/Transforms/SimplifyCFG/trap-no-null-opt-debugloc.ll @@ -0,0 +1,24 @@ +; RUN: opt -S -simplifycfg < %s | FileCheck %s +define void @foo() nounwind ssp #0 !dbg !0 { +; CHECK: store i32 42, i32* null +; CHECK-NOT: call void @llvm.trap() +; CHECK: ret void + store i32 42, i32* null, !dbg !5 + ret void, !dbg !7 +} + +attributes #0 = { "null-pointer-is-valid"="true" } + +!llvm.dbg.cu = !{!2} +!llvm.module.flags = !{!10} + +!0 = distinct !DISubprogram(name: "foo", line: 3, isLocal: false, isDefinition: true, virtualIndex: 6, isOptimized: false, unit: !2, file: !8, scope: !1, type: !3) +!1 = !DIFile(filename: "foo.c", directory: "/private/tmp") +!2 = distinct !DICompileUnit(language: DW_LANG_C99, producer: "Apple clang version 3.0 (tags/Apple/clang-206.1) (based on LLVM 3.0svn)", isOptimized: true, emissionKind: FullDebug, file: !8, enums: !{}, retainedTypes: !{}) +!3 = !DISubroutineType(types: !4) +!4 = !{null} +!5 = !DILocation(line: 4, column: 2, scope: !6) +!6 = distinct !DILexicalBlock(line: 3, column: 12, file: !8, scope: !0) +!7 = !DILocation(line: 5, column: 1, scope: !6) +!8 = !DIFile(filename: "foo.c", directory: "/private/tmp") +!10 = !{i32 1, !"Debug Info Version", i32 3} Index: test/Transforms/SimplifyCFG/trapping-load-unreachable.ll =================================================================== --- test/Transforms/SimplifyCFG/trapping-load-unreachable.ll +++ test/Transforms/SimplifyCFG/trapping-load-unreachable.ll @@ -21,17 +21,44 @@ ; CHECK: load volatile } +define void @test1_no_null_opt(i32 %x) nounwind #0 { +entry: + %0 = icmp eq i32 %x, 0 ; [#uses=1] + br i1 %0, label %bb, label %return + +bb: ; preds = %entry + %1 = load volatile i32, i32* null + unreachable + + br label %return +return: ; preds = %entry + ret void +; CHECK-LABEL: @test1_no_null_opt( +; CHECK: load volatile +; CHECK: unreachable +} + ; rdar://7958343 define void @test2() nounwind { entry: store i32 4,i32* null ret void - + ; CHECK-LABEL: @test2( ; CHECK: call void @llvm.trap ; CHECK: unreachable } +define void @test2_no_null_opt() nounwind #0 { +entry: + store i32 4,i32* null + ret void +; CHECK-LABEL: @test2_no_null_opt( +; CHECK: store i32 4, i32* null +; CHECK-NOT: call void @llvm.trap +; CHECK: ret +} + ; PR7369 define void @test3() nounwind { entry: @@ -43,6 +70,16 @@ ; CHECK: ret } +define void @test3_no_null_opt() nounwind #0 { +entry: + store volatile i32 4, i32* null + ret void + +; CHECK-LABEL: @test3_no_null_opt( +; CHECK: store volatile i32 4, i32* null +; CHECK: ret +} + ; Check store before unreachable. define void @test4(i1 %C, i32* %P) { ; CHECK-LABEL: @test4( @@ -85,3 +122,4 @@ ret void } +attributes #0 = { "null-pointer-is-valid"="true" }