Index: llvm/trunk/include/llvm/Transforms/IPO/Attributor.h =================================================================== --- llvm/trunk/include/llvm/Transforms/IPO/Attributor.h +++ llvm/trunk/include/llvm/Transforms/IPO/Attributor.h @@ -97,6 +97,7 @@ #define LLVM_TRANSFORMS_IPO_ATTRIBUTOR_H #include "llvm/ADT/SetVector.h" +#include "llvm/Analysis/TargetLibraryInfo.h" #include "llvm/ADT/MapVector.h" #include "llvm/IR/CallSite.h" #include "llvm/IR/PassManager.h" @@ -550,6 +551,14 @@ return FuncRWInstsMap[&F]; } + /// Return TargetLibraryInfo for function \p F. + TargetLibraryInfo *getTargetLibraryInfoForFunction(const Function &F) { + return FuncTLIMap[&F]; + } + + /// Return datalayout used in the module. + const DataLayout &getDL() { return DL; } + private: /// A map type from functions to opcode to instruction maps. using FuncInstOpcodeMapTy = DenseMap; @@ -557,6 +566,9 @@ /// A map type from functions to their read or write instructions. using FuncRWInstsMapTy = DenseMap; + /// A map type from functions to their TLI. + using FuncTLIMapTy = DenseMap; + /// A nested map that remembers all instructions in a function with a certain /// instruction opcode (Instruction::getOpcode()). FuncInstOpcodeMapTy FuncInstOpcodeMap; @@ -564,6 +576,9 @@ /// A map from functions to their instructions that may read or write memory. FuncRWInstsMapTy FuncRWInstsMap; + /// A map from functions to their TLI. + FuncTLIMapTy FuncTLIMap; + /// The datalayout used in the module. const DataLayout &DL; @@ -689,13 +704,15 @@ /// abstract attribute objects for them. /// /// \param F The function that is checked for attribute opportunities. + /// \param TLIGetter helper function to get TargetLibraryInfo Analysis result. /// /// Note that abstract attribute instances are generally created even if the /// IR already contains the information they would deduce. The most important /// reason for this is the single interface, the one of the abstract attribute /// instance, which can be queried without the need to look at the IR in /// various places. - void identifyDefaultAbstractAttributes(Function &F); + void identifyDefaultAbstractAttributes( + Function &F, std::function &TLIGetter); /// Mark the internal function \p F as live. /// @@ -704,7 +721,11 @@ void markLiveInternalFunction(const Function &F) { assert(F.hasInternalLinkage() && "Only internal linkage is assumed dead initially."); - identifyDefaultAbstractAttributes(const_cast(F)); + + std::function TLIGetter = + [&](Function &F) -> TargetLibraryInfo * { return nullptr; }; + + identifyDefaultAbstractAttributes(const_cast(F), TLIGetter); } /// Record that \p I is deleted after information was manifested. @@ -1725,6 +1746,30 @@ static const char ID; }; +struct AAHeapToStack : public StateWrapper, + public IRPosition { + AAHeapToStack(const IRPosition &IRP) : IRPosition(IRP) {} + + /// Returns true if HeapToStack conversion is assumed to be possible. + bool isAssumedHeapToStack() const { return getAssumed(); } + + /// Returns true if HeapToStack conversion is known to be possible. + bool isKnownHeapToStack() const { return getKnown(); } + + /// Return an IR position, see struct IRPosition. + /// + ///{ + IRPosition &getIRPosition() { return *this; } + const IRPosition &getIRPosition() const { return *this; } + ///} + + /// Create an abstract attribute view for the position \p IRP. + static AAHeapToStack &createForPosition(const IRPosition &IRP, Attributor &A); + + /// Unique ID (due to the unique address) + static const char ID; +}; + } // end namespace llvm #endif // LLVM_TRANSFORMS_IPO_FUNCTIONATTRS_H Index: llvm/trunk/lib/Transforms/IPO/Attributor.cpp =================================================================== --- llvm/trunk/lib/Transforms/IPO/Attributor.cpp +++ llvm/trunk/lib/Transforms/IPO/Attributor.cpp @@ -24,6 +24,7 @@ #include "llvm/Analysis/EHPersonalities.h" #include "llvm/Analysis/GlobalsModRef.h" #include "llvm/Analysis/Loads.h" +#include "llvm/Analysis/MemoryBuiltins.h" #include "llvm/Analysis/ValueTracking.h" #include "llvm/IR/Argument.h" #include "llvm/IR/Attributes.h" @@ -135,6 +136,12 @@ cl::desc("Number of iterations until dependences are recomputed."), cl::init(4)); +static cl::opt EnableHeapToStack("enable-heap-to-stack-conversion", + cl::init(true), cl::Hidden); + +static cl::opt MaxHeapToStackSize("max-heap-to-stack-size", + cl::init(128), cl::Hidden); + /// Logic operators for the change status enum class. /// ///{ @@ -3185,6 +3192,212 @@ } }; +/// ----------------------- Heap-To-Stack Conversion --------------------------- +struct AAHeapToStackImpl : public AAHeapToStack { + AAHeapToStackImpl(const IRPosition &IRP) : AAHeapToStack(IRP) {} + + const std::string getAsStr() const override { + return "[H2S] Mallocs: " + std::to_string(MallocCalls.size()); + } + + ChangeStatus manifest(Attributor &A) override { + assert(getState().isValidState() && + "Attempted to manifest an invalid state!"); + + ChangeStatus HasChanged = ChangeStatus::UNCHANGED; + Function *F = getAssociatedFunction(); + const auto *TLI = A.getInfoCache().getTargetLibraryInfoForFunction(*F); + + for (Instruction *MallocCall : MallocCalls) { + // This malloc cannot be replaced. + if (BadMallocCalls.count(MallocCall)) + continue; + + for (Instruction *FreeCall : FreesForMalloc[MallocCall]) { + LLVM_DEBUG(dbgs() << "H2S: Removing free call: " << *FreeCall << "\n"); + A.deleteAfterManifest(*FreeCall); + HasChanged = ChangeStatus::CHANGED; + } + + LLVM_DEBUG(dbgs() << "H2S: Removing malloc call: " << *MallocCall + << "\n"); + + Constant *Size; + if (isCallocLikeFn(MallocCall, TLI)) { + auto *Num = cast(MallocCall->getOperand(0)); + auto *SizeT = dyn_cast(MallocCall->getOperand(1)); + APInt TotalSize = SizeT->getValue() * Num->getValue(); + Size = + ConstantInt::get(MallocCall->getOperand(0)->getType(), TotalSize); + } else { + Size = cast(MallocCall->getOperand(0)); + } + + unsigned AS = cast(MallocCall->getType())->getAddressSpace(); + Instruction *AI = new AllocaInst(Type::getInt8Ty(F->getContext()), AS, + Size, "", MallocCall->getNextNode()); + + if (AI->getType() != MallocCall->getType()) + AI = new BitCastInst(AI, MallocCall->getType(), "malloc_bc", + AI->getNextNode()); + + MallocCall->replaceAllUsesWith(AI); + + if (auto *II = dyn_cast(MallocCall)) { + auto *NBB = II->getNormalDest(); + BranchInst::Create(NBB, MallocCall->getParent()); + A.deleteAfterManifest(*MallocCall); + } else { + A.deleteAfterManifest(*MallocCall); + } + + if (isCallocLikeFn(MallocCall, TLI)) { + auto *BI = new BitCastInst(AI, MallocCall->getType(), "calloc_bc", + AI->getNextNode()); + Value *Ops[] = { + BI, ConstantInt::get(F->getContext(), APInt(8, 0, false)), Size, + ConstantInt::get(Type::getInt1Ty(F->getContext()), false)}; + + Type *Tys[] = {BI->getType(), MallocCall->getOperand(0)->getType()}; + Module *M = F->getParent(); + Function *Fn = Intrinsic::getDeclaration(M, Intrinsic::memset, Tys); + CallInst::Create(Fn, Ops, "", BI->getNextNode()); + } + HasChanged = ChangeStatus::CHANGED; + } + + return HasChanged; + } + + /// Collection of all malloc calls in a function. + SmallSetVector MallocCalls; + + /// Collection of malloc calls that cannot be converted. + DenseSet BadMallocCalls; + + /// A map for each malloc call to the set of associated free calls. + DenseMap> FreesForMalloc; + + ChangeStatus updateImpl(Attributor &A) override; +}; + +ChangeStatus AAHeapToStackImpl::updateImpl(Attributor &A) { + const Function *F = getAssociatedFunction(); + const auto *TLI = A.getInfoCache().getTargetLibraryInfoForFunction(*F); + + auto UsesCheck = [&](Instruction &I) { + SmallPtrSet Visited; + SmallVector Worklist; + + for (Use &U : I.uses()) + Worklist.push_back(&U); + + while (!Worklist.empty()) { + const Use *U = Worklist.pop_back_val(); + if (!Visited.insert(U).second) + continue; + + auto *UserI = U->getUser(); + + if (isa(UserI) || isa(UserI)) + continue; + + // NOTE: Right now, if a function that has malloc pointer as an argument + // frees memory, we assume that the malloc pointer is freed. + + // TODO: Add nofree callsite argument attribute to indicate that pointer + // argument is not freed. + if (auto *CB = dyn_cast(UserI)) { + if (!CB->isArgOperand(U)) + continue; + + if (CB->isLifetimeStartOrEnd()) + continue; + + // Record malloc. + if (isFreeCall(UserI, TLI)) { + FreesForMalloc[&I].insert( + cast(const_cast(UserI))); + continue; + } + + // If a function does not free memory we are fine + const auto &NoFreeAA = + A.getAAFor(*this, IRPosition::callsite_function(*CB)); + + unsigned ArgNo = U - CB->arg_begin(); + const auto &NoCaptureAA = A.getAAFor( + *this, IRPosition::callsite_argument(*CB, ArgNo)); + + if (!NoCaptureAA.isAssumedNoCapture() || !NoFreeAA.isAssumedNoFree()) { + LLVM_DEBUG(dbgs() << "[H2S] Bad user: " << *UserI << "\n"); + return false; + } + continue; + } + + if (isa(UserI) || isa(UserI)) { + for (Use &U : UserI->uses()) + Worklist.push_back(&U); + continue; + } + + // Unknown user. + LLVM_DEBUG(dbgs() << "[H2S] Unknown user: " << *UserI << "\n"); + return false; + } + return true; + }; + + auto MallocCallocCheck = [&](Instruction &I) { + if (isMallocLikeFn(&I, TLI)) { + if (auto *Size = dyn_cast(I.getOperand(0))) + if (!Size->getValue().sle(MaxHeapToStackSize)) + return true; + } else if (isCallocLikeFn(&I, TLI)) { + bool Overflow = false; + if (auto *Num = dyn_cast(I.getOperand(0))) + if (auto *Size = dyn_cast(I.getOperand(1))) + if (!(Size->getValue().umul_ov(Num->getValue(), Overflow)) + .sle(MaxHeapToStackSize)) + if (!Overflow) + return true; + } else { + BadMallocCalls.insert(&I); + return true; + } + + if (BadMallocCalls.count(&I)) + return true; + + if (UsesCheck(I)) + MallocCalls.insert(&I); + else + BadMallocCalls.insert(&I); + return true; + }; + + size_t NumBadMallocs = BadMallocCalls.size(); + + A.checkForAllCallLikeInstructions(MallocCallocCheck, *this); + + if (NumBadMallocs != BadMallocCalls.size()) + return ChangeStatus::CHANGED; + + return ChangeStatus::UNCHANGED; +} + +struct AAHeapToStackFunction final : public AAHeapToStackImpl { + AAHeapToStackFunction(const IRPosition &IRP) : AAHeapToStackImpl(IRP) {} + + /// See AbstractAttribute::trackStatistics() + void trackStatistics() const override { + STATS_DECL(MallocCalls, Function, + "Number of MallocCalls converted to allocas"); + BUILD_STAT_NAME(MallocCalls, Function) += MallocCalls.size(); + } +}; + /// ---------------------------------------------------------------------------- /// Attributor /// ---------------------------------------------------------------------------- @@ -3632,10 +3845,14 @@ return ManifestChange; } -void Attributor::identifyDefaultAbstractAttributes(Function &F) { +void Attributor::identifyDefaultAbstractAttributes( + Function &F, std::function &TLIGetter) { if (!VisitedFunctions.insert(&F).second) return; + if (EnableHeapToStack) + InfoCache.FuncTLIMap[&F] = TLIGetter(F); + IRPosition FPos = IRPosition::function(F); // Check for dead BasicBlocks in every function. @@ -3658,6 +3875,10 @@ // Every function might be "no-return". getOrCreateAAFor(FPos); + // Every function might be applicable for Heap-To-Stack conversion. + if (EnableHeapToStack) + getOrCreateAAFor(FPos); + // Return attributes are only appropriate if the return type is non void. Type *ReturnType = F.getReturnType(); if (!ReturnType->isVoidTy()) { @@ -3842,7 +4063,8 @@ /// Pass (Manager) Boilerplate /// ---------------------------------------------------------------------------- -static bool runAttributorOnModule(Module &M) { +static bool runAttributorOnModule( + Module &M, std::function &TLIGetter) { if (DisableAttributor) return false; @@ -3877,14 +4099,21 @@ // Populate the Attributor with abstract attribute opportunities in the // function and the information cache with IR information. - A.identifyDefaultAbstractAttributes(F); + A.identifyDefaultAbstractAttributes(F, TLIGetter); } return A.run(M) == ChangeStatus::CHANGED; } PreservedAnalyses AttributorPass::run(Module &M, ModuleAnalysisManager &AM) { - if (runAttributorOnModule(M)) { + auto &FAM = AM.getResult(M).getManager(); + + std::function TLIGetter = + [&](Function &F) -> TargetLibraryInfo * { + return &FAM.getResult(F); + }; + + if (runAttributorOnModule(M, TLIGetter)) { // FIXME: Think about passes we will preserve and add them here. return PreservedAnalyses::none(); } @@ -3903,11 +4132,15 @@ bool runOnModule(Module &M) override { if (skipModule(M)) return false; - return runAttributorOnModule(M); + std::function TLIGetter = + [&](Function &F) -> TargetLibraryInfo * { return nullptr; }; + + return runAttributorOnModule(M, TLIGetter); } void getAnalysisUsage(AnalysisUsage &AU) const override { // FIXME: Think about passes we will preserve and add them here. + AU.addRequired(); } }; @@ -3931,6 +4164,7 @@ const char AAAlign::ID = 0; const char AANoCapture::ID = 0; const char AAValueSimplify::ID = 0; +const char AAHeapToStack::ID = 0; // Macro magic to create the static generator function for attributes that // follow the naming scheme. @@ -3992,6 +4226,23 @@ return *AA; \ } +#define CREATE_FUNCTION_ONLY_ABSTRACT_ATTRIBUTE_FOR_POSITION(CLASS) \ + CLASS &CLASS::createForPosition(const IRPosition &IRP, Attributor &A) { \ + CLASS *AA = nullptr; \ + switch (IRP.getPositionKind()) { \ + SWITCH_PK_INV(CLASS, IRP_INVALID, "invalid") \ + SWITCH_PK_INV(CLASS, IRP_ARGUMENT, "argument") \ + SWITCH_PK_INV(CLASS, IRP_FLOAT, "floating") \ + SWITCH_PK_INV(CLASS, IRP_RETURNED, "returned") \ + SWITCH_PK_INV(CLASS, IRP_CALL_SITE_RETURNED, "call site returned") \ + SWITCH_PK_INV(CLASS, IRP_CALL_SITE_ARGUMENT, "call site argument") \ + SWITCH_PK_INV(CLASS, IRP_CALL_SITE, "call site") \ + SWITCH_PK_CREATE(CLASS, IRP, IRP_FUNCTION, Function) \ + } \ + AA->initialize(A); \ + return *AA; \ + } + CREATE_FUNCTION_ABSTRACT_ATTRIBUTE_FOR_POSITION(AANoUnwind) CREATE_FUNCTION_ABSTRACT_ATTRIBUTE_FOR_POSITION(AANoSync) CREATE_FUNCTION_ABSTRACT_ATTRIBUTE_FOR_POSITION(AANoFree) @@ -4009,6 +4260,8 @@ CREATE_ALL_ABSTRACT_ATTRIBUTE_FOR_POSITION(AAValueSimplify) +CREATE_FUNCTION_ONLY_ABSTRACT_ATTRIBUTE_FOR_POSITION(AAHeapToStack) + #undef CREATE_FUNCTION_ABSTRACT_ATTRIBUTE_FOR_POSITION #undef CREATE_VALUE_ABSTRACT_ATTRIBUTE_FOR_POSITION #undef CREATE_ALL_ABSTRACT_ATTRIBUTE_FOR_POSITION @@ -4017,5 +4270,6 @@ INITIALIZE_PASS_BEGIN(AttributorLegacyPass, "attributor", "Deduce and propagate attributes", false, false) +INITIALIZE_PASS_DEPENDENCY(TargetLibraryInfoWrapperPass) INITIALIZE_PASS_END(AttributorLegacyPass, "attributor", "Deduce and propagate attributes", false, false) Index: llvm/trunk/test/Transforms/FunctionAttrs/heap_to_stack.ll =================================================================== --- llvm/trunk/test/Transforms/FunctionAttrs/heap_to_stack.ll +++ llvm/trunk/test/Transforms/FunctionAttrs/heap_to_stack.ll @@ -0,0 +1,318 @@ +; RUN: opt -passes=attributor --attributor-disable=false -S < %s | FileCheck %s + +declare noalias i8* @malloc(i64) + +declare void @nocapture_func_frees_pointer(i8* nocapture) + +declare void @func_throws(...) + +declare void @sync_func(i8* %p) + +declare void @sync_will_return(i8* %p) willreturn + +declare void @no_sync_func(i8* nocapture %p) nofree nosync willreturn + +declare void @nofree_func(i8* nocapture %p) nofree nosync willreturn + +declare void @foo(i32* %p) + +declare void @foo_nounw(i32* %p) nounwind nofree + +declare i32 @no_return_call() noreturn + +declare void @free(i8* nocapture) + +declare void @llvm.lifetime.start.p0i8(i64, i8* nocapture) nounwind + +; TEST 1 - negative, pointer freed in another function. + +define void @test1() { + %1 = tail call noalias i8* @malloc(i64 4) + ; CHECK: @malloc(i64 4) + ; CHECK-NEXT: @nocapture_func_frees_pointer(i8* noalias nocapture %1) + tail call void @nocapture_func_frees_pointer(i8* %1) + tail call void (...) @func_throws() + tail call void @free(i8* %1) + ret void +} + +; TEST 2 - negative, call to a sync function. + +define void @test2() { + %1 = tail call noalias i8* @malloc(i64 4) + ; CHECK: @malloc(i64 4) + ; CHECK-NEXT: @sync_func(i8* %1) + tail call void @sync_func(i8* %1) + tail call void @free(i8* %1) + ret void +} + +; TEST 3 - 1 malloc, 1 free + +define void @test3() { + %1 = tail call noalias i8* @malloc(i64 4) + ; CHECK: %1 = alloca i8, i64 4 + ; CHECK-NEXT: @no_sync_func(i8* noalias nocapture %1) + tail call void @no_sync_func(i8* %1) + ; CHECK-NOT: @free(i8* %1) + tail call void @free(i8* %1) + ret void +} + +declare noalias i8* @calloc(i64, i64) + +define void @test0() { + %1 = tail call noalias i8* @calloc(i64 2, i64 4) + ; CHECK: %1 = alloca i8, i64 8 + ; CHECK-NEXT: %calloc_bc = bitcast i8* %1 to i8* + ; CHECK-NEXT: call void @llvm.memset.p0i8.i64(i8* %calloc_bc, i8 0, i64 8, i1 false) + ; CHECK-NEXT: @no_sync_func(i8* noalias nocapture %1) + tail call void @no_sync_func(i8* %1) + ; CHECK-NOT: @free(i8* %1) + tail call void @free(i8* %1) + ret void +} + +; TEST 4 +define void @test4() { + %1 = tail call noalias i8* @malloc(i64 4) + ; CHECK: %1 = alloca i8, i64 4 + ; CHECK-NEXT: @nofree_func(i8* noalias nocapture %1) + tail call void @nofree_func(i8* %1) + ret void +} + +; TEST 5 - not all exit paths have a call to free, but all uses of malloc +; are in nofree functions and are not captured + +define void @test5(i32) { + %2 = tail call noalias i8* @malloc(i64 4) + ; CHECK: %2 = alloca i8, i64 4 + ; CHECK-NEXT: icmp eq i32 %0, 0 + %3 = icmp eq i32 %0, 0 + br i1 %3, label %5, label %4 + +4: ; preds = %1 + tail call void @nofree_func(i8* %2) + br label %6 + +5: ; preds = %1 + tail call void @free(i8* %2) + ; CHECK-NOT: @free(i8* %2) + br label %6 + +6: ; preds = %5, %4 + ret void +} + +; TEST 6 - all exit paths have a call to free + +define void @test6(i32) { + %2 = tail call noalias i8* @malloc(i64 4) + ; CHECK: %2 = alloca i8, i64 4 + ; CHECK-NEXT: icmp eq i32 %0, 0 + %3 = icmp eq i32 %0, 0 + br i1 %3, label %5, label %4 + +4: ; preds = %1 + tail call void @nofree_func(i8* %2) + tail call void @free(i8* %2) + ; CHECK-NOT: @free(i8* %2) + br label %6 + +5: ; preds = %1 + tail call void @free(i8* %2) + ; CHECK-NOT: @free(i8* %2) + br label %6 + +6: ; preds = %5, %4 + ret void +} + +; TEST 7 - free is dead. + +define void @test7() { + %1 = tail call noalias i8* @malloc(i64 4) + ; CHECK: alloca i8, i64 4 + ; CHECK-NEXT: tail call i32 @no_return_call() + tail call i32 @no_return_call() + ; CHECK-NOT: @free(i8* %1) + tail call void @free(i8* %1) + ret void +} + +; TEST 8 - Negative: bitcast pointer used in capture function + +define void @test8() { + %1 = tail call noalias i8* @malloc(i64 4) + ; CHECK: %1 = tail call noalias i8* @malloc(i64 4) + ; CHECK-NEXT: @no_sync_func(i8* nocapture %1) + tail call void @no_sync_func(i8* %1) + %2 = bitcast i8* %1 to i32* + store i32 10, i32* %2 + %3 = load i32, i32* %2 + tail call void @foo(i32* %2) + ; CHECK: @free(i8* %1) + tail call void @free(i8* %1) + ret void +} + +; TEST 9 - FIXME: malloc should be converted. +define void @test9() { + %1 = tail call noalias i8* @malloc(i64 4) + ; CHECK: %1 = tail call noalias i8* @malloc(i64 4) + ; CHECK-NEXT: @no_sync_func(i8* nocapture %1) + tail call void @no_sync_func(i8* %1) + %2 = bitcast i8* %1 to i32* + store i32 10, i32* %2 + %3 = load i32, i32* %2 + tail call void @foo_nounw(i32* %2) + ; CHECK: @free(i8* %1) + tail call void @free(i8* %1) + ret void +} + +; TEST 10 - 1 malloc, 1 free + +define i32 @test10() { + %1 = tail call noalias i8* @malloc(i64 4) + ; CHECK: %1 = alloca i8, i64 4 + ; CHECK-NEXT: @no_sync_func(i8* noalias nocapture %1) + tail call void @no_sync_func(i8* %1) + %2 = bitcast i8* %1 to i32* + store i32 10, i32* %2 + %3 = load i32, i32* %2 + ; CHECK-NOT: @free(i8* %1) + tail call void @free(i8* %1) + ret i32 %3 +} + +define i32 @test_lifetime() { + %1 = tail call noalias i8* @malloc(i64 4) + ; CHECK: %1 = alloca i8, i64 4 + ; CHECK-NEXT: @no_sync_func(i8* noalias nocapture %1) + tail call void @no_sync_func(i8* %1) + call void @llvm.lifetime.start.p0i8(i64 4, i8* %1) + %2 = bitcast i8* %1 to i32* + store i32 10, i32* %2 + %3 = load i32, i32* %2 + ; CHECK-NOT: @free(i8* %1) + tail call void @free(i8* %1) + ret i32 %3 +} + +; TEST 11 +; FIXME: should be ok + +define void @test11() { + %1 = tail call noalias i8* @malloc(i64 4) + ; CHECK: @malloc(i64 4) + ; CHECK-NEXT: @sync_will_return(i8* %1) + tail call void @sync_will_return(i8* %1) + tail call void @free(i8* %1) + ret void +} + +; TEST 12 +define i32 @irreducible_cfg(i32 %0) { + %2 = alloca i32, align 4 + %3 = alloca i32*, align 8 + %4 = alloca i32, align 4 + store i32 %0, i32* %2, align 4 + %5 = call noalias i8* @malloc(i64 4) #2 + ; CHECK: alloca i8, i64 4 + ; CHECK-NEXT: %6 = bitcast + %6 = bitcast i8* %5 to i32* + store i32* %6, i32** %3, align 8 + %7 = load i32*, i32** %3, align 8 + store i32 10, i32* %7, align 4 + %8 = load i32, i32* %2, align 4 + %9 = icmp eq i32 %8, 1 + br i1 %9, label %10, label %13 + +10: ; preds = %1 + %11 = load i32, i32* %2, align 4 + %12 = add nsw i32 %11, 5 + store i32 %12, i32* %2, align 4 + br label %20 + +13: ; preds = %1 + store i32 1, i32* %2, align 4 + br label %14 + +14: ; preds = %20, %13 + %15 = load i32*, i32** %3, align 8 + %16 = load i32, i32* %15, align 4 + %17 = add nsw i32 %16, -1 + store i32 %17, i32* %15, align 4 + %18 = icmp ne i32 %16, 0 + br i1 %18, label %19, label %23 + +19: ; preds = %14 + br label %20 + +20: ; preds = %19, %10 + %21 = load i32, i32* %2, align 4 + %22 = add nsw i32 %21, 1 + store i32 %22, i32* %2, align 4 + br label %14 + +23: ; preds = %14 + %24 = load i32*, i32** %3, align 8 + %25 = load i32, i32* %24, align 4 + store i32 %25, i32* %4, align 4 + %26 = load i32*, i32** %3, align 8 + %27 = bitcast i32* %26 to i8* + call void @free(i8* %27) #2 + %28 = load i32*, i32** %3, align 8 + %29 = load i32, i32* %28, align 4 + ret i32 %29 +} + +define i32 @malloc_in_loop(i32 %0) { + %2 = alloca i32, align 4 + %3 = alloca i32*, align 8 + store i32 %0, i32* %2, align 4 + br label %4 + +4: ; preds = %8, %1 + %5 = load i32, i32* %2, align 4 + %6 = add nsw i32 %5, -1 + store i32 %6, i32* %2, align 4 + %7 = icmp sgt i32 %6, 0 + br i1 %7, label %8, label %11 + +8: ; preds = %4 + %9 = call noalias i8* @malloc(i64 4) + ; CHECK: alloca i8, i64 4 + %10 = bitcast i8* %9 to i32* + store i32* %10, i32** %3, align 8 + br label %4 + +11: ; preds = %4 + ret i32 5 +} + +; Malloc/Calloc too large +define i32 @test13() { + %1 = tail call noalias i8* @malloc(i64 256) + ; CHECK: %1 = tail call noalias i8* @malloc(i64 256) + ; CHECK-NEXT: @no_sync_func(i8* noalias %1) + tail call void @no_sync_func(i8* %1) + %2 = bitcast i8* %1 to i32* + store i32 10, i32* %2 + %3 = load i32, i32* %2 + tail call void @free(i8* %1) + ; CHECK: tail call void @free(i8* noalias %1) + ret i32 %3 +} + +define void @test14() { + %1 = tail call noalias i8* @calloc(i64 64, i64 4) + ; CHECK: %1 = tail call noalias i8* @calloc(i64 64, i64 4) + ; CHECK-NEXT: @no_sync_func(i8* noalias %1) + tail call void @no_sync_func(i8* %1) + tail call void @free(i8* %1) + ; CHECK: tail call void @free(i8* noalias %1) + ret void +}