Index: lib/Transforms/IPO/FunctionAttrs.cpp =================================================================== --- lib/Transforms/IPO/FunctionAttrs.cpp +++ lib/Transforms/IPO/FunctionAttrs.cpp @@ -741,6 +741,147 @@ return true; } +/// Tests whether a function is "printf-like". +/// +/// A function is "printf-like" if it uses varargs and the only use of the +/// variable argument list is to pass to a vsprintf-like function. We know +/// vsprintf and friends do not capture or write their variadic arguments, +/// so can propagate readonly+nocapture to all calls of F. +static bool isPrintfLikeFunction(Function *F, const TargetLibraryInfo &TLI) { + if (!F->isVarArg()) + return false; + + // We search for a call to @llvm.vastart(i8* %x). If %x isn't an alloca we + // bail out. + // Then we search for all uses of %x, following through bitcasts. We use a + // CaptureTracker to identify uses of %x as call operands, then later check + // that the operand is to a call to a known library function such as vsprintf. + + // Find the va_start call. + Instruction *VaStartInst = nullptr; + for (auto &I : F->getEntryBlock()) + if (auto *Intr = dyn_cast(&I)) + if (Intr->getIntrinsicID() == Intrinsic::vastart) { + if (VaStartInst) + // Multiple va_starts seen - we only support one. + return false; + VaStartInst = Intr; + } + if (!VaStartInst) + return false; + + const DataLayout &DL = F->getParent()->getDataLayout(); + Value *UnderlyingVaStart = + GetUnderlyingObject(VaStartInst->getOperand(0), DL); + + if (!isa(UnderlyingVaStart)) + return false; + + struct ValistCaptureTracker : public CaptureTracker { + bool Captured = false; + const Use *Call = nullptr; + + ~ValistCaptureTracker() {} + + void tooManyUses() override { Captured = true; } + + bool shouldExplore(const Use *U) override { + if (auto *LI = dyn_cast(U->getUser())) { + // The value has been loaded. Is it (only) used in a call? + Value *V = LI; + // Spool through single-use instructions if needed. + while (V->hasOneUse() && !isa(*V->user_begin())) + V = *V->user_begin(); + if (V->hasOneUse() && isa(*V->user_begin())) { + if (Call) + // Multiple calls seen! + Captured = true; + else + Call = &*V->use_begin(); + } else { + // Loaded value is used in multiple places or not in a call. + Captured = true; + } + } + + return !Captured; + } + bool captured(const Use *U) override { + if (auto *I = dyn_cast(U->getUser())) + if (I->getIntrinsicID() == Intrinsic::vastart || + I->getIntrinsicID() == Intrinsic::vaend) + // Not captured, keep going. + return false; + + if (Operator::getOpcode(U->getUser()) == Instruction::BitCast) + // Not captured, keep going. + return false; + + Captured = true; + return true; + } + }; + ValistCaptureTracker Tracker; + PointerMayBeCaptured(UnderlyingVaStart, &Tracker); + + if (Tracker.Captured || !Tracker.Call) + return false; + + // Now, is the call to a known va_list function? + Function *CalledF = + cast(Tracker.Call->getUser())->getCalledFunction(); + if (!CalledF) + return false; + + LibFunc::Func TheLibFunc; + if (!(TLI.getLibFunc(CalledF->getName(), TheLibFunc) && TLI.has(TheLibFunc))) + return false; + + switch (TheLibFunc) { + default: + return false; + case LibFunc::vprintf: + case LibFunc::vsprintf: + case LibFunc::vfprintf: + case LibFunc::vsnprintf: + return true; + } +} + +// Deduce attributes for variadic arguments in the SCC. +static bool addVarArgAttrs(const SCCNodeSet &SCCNodes, + const TargetLibraryInfo &TLI) { + // Look for functions whose variadic arguments only flow to libcalls known + // not to capture or write them, and add nocapture+readonly to their + // callsites. + // + // FIXME: This can be extended to work for vscanf and friends, adding + // nocapture (but not readonly). + bool Changed = false; + for (Function *F : SCCNodes) { + if (F->isDeclaration() || F->mayBeOverridden()) + continue; + if (!isPrintfLikeFunction(F, TLI)) + continue; + + // Propagate readonly+nocapture to all arguments. + for (auto *U : F->users()) + if (CallInst *CI = dyn_cast(U)) + for (unsigned I = 1; I < CI->getNumOperands(); ++I) + if (CI->getOperand(I - 1)->getType()->isPointerTy()) { + if (!CI->paramHasAttr(I, Attribute::NoCapture)) { + Changed = true; + CI->addAttribute(I, Attribute::NoCapture); + } + if (!CI->paramHasAttr(I, Attribute::ReadOnly)) { + Changed = true; + CI->addAttribute(I, Attribute::ReadOnly); + } + } + } + return Changed; +} + /// Deduce noalias attributes for the SCC. static bool addNoAliasAttrs(const SCCNodeSet &SCCNodes) { // Check each function in turn, determining which functions return noalias @@ -1878,7 +2019,8 @@ Changed |= addNoAliasAttrs(SCCNodes); Changed |= addNonNullAttrs(SCCNodes, *TLI); } - + + Changed |= addVarArgAttrs(SCCNodes, *TLI); Changed |= addNoRecurseAttrs(SCC, Revisit); return Changed; } Index: test/Transforms/FunctionAttrs/varargs.ll =================================================================== --- /dev/null +++ test/Transforms/FunctionAttrs/varargs.ll @@ -0,0 +1,34 @@ +; RUN: opt < %s -functionattrs -S | FileCheck %s + +target triple = "armv7--linux-gnueabihf" + +%struct.__va_list = type { i8* } + +declare i32 @vsprintf(i8* readonly nocapture, i8* readonly nocapture, [1 x i32]) + +; @f() is "printf-like", so any varargs operands to it should be readonly+nocapture. +define i32 @f(i8* nocapture %a, i8* nocapture readonly %b, ...) #0 { + %v = alloca %struct.__va_list, align 4 + %1 = bitcast %struct.__va_list* %v to i8* + call void @llvm.lifetime.start(i64 4, i8* %1) + call void @llvm.va_start(i8* %1) + %2 = bitcast %struct.__va_list* %v to i32* + %3 = load i32, i32* %2, align 4 + %4 = insertvalue [1 x i32] undef, i32 %3, 0 + %5 = call arm_aapcscc i32 @vsprintf(i8* %a, i8* %b, [1 x i32] %4) #1 + call void @llvm.va_end(i8* %1) + call void @llvm.lifetime.end(i64 4, i8* %1) + ret i32 %5 +} + +; CHECK-LABEL: @g +; CHECK: call i32 (i8*, i8*, ...) @f(i8* %a, i8* %b, i8* nocapture readonly %c, i32 %d) +define i32 @g(i8* %a, i8* %b, i8* %c, i32 %d) { + %x = call i32 (i8*,i8*, ...) @f(i8* %a, i8* %b, i8* %c, i32 %d) + ret i32 %x +} + +declare void @llvm.lifetime.start(i64, i8* nocapture) +declare void @llvm.lifetime.end(i64, i8* nocapture) +declare void @llvm.va_start(i8*) +declare void @llvm.va_end(i8*)