diff --git a/llvm/include/llvm/Transforms/Utils/SimplifyLibCalls.h b/llvm/include/llvm/Transforms/Utils/SimplifyLibCalls.h --- a/llvm/include/llvm/Transforms/Utils/SimplifyLibCalls.h +++ b/llvm/include/llvm/Transforms/Utils/SimplifyLibCalls.h @@ -222,6 +222,8 @@ Value *optimizePuts(CallInst *CI, IRBuilderBase &B); // Helper methods + Value* emitSnPrintfMemCpy(CallInst *CI, Value *StrArg, StringRef Str, + uint64_t N, IRBuilderBase &B); Value *emitStrLenMemCpy(Value *Src, Value *Dst, uint64_t Len, IRBuilderBase &B); void classifyArgUse(Value *Val, Function *F, bool IsFloat, diff --git a/llvm/lib/Transforms/Utils/SimplifyLibCalls.cpp b/llvm/lib/Transforms/Utils/SimplifyLibCalls.cpp --- a/llvm/lib/Transforms/Utils/SimplifyLibCalls.cpp +++ b/llvm/lib/Transforms/Utils/SimplifyLibCalls.cpp @@ -2908,6 +2908,60 @@ return nullptr; } +// Transform an snprintf call CI with the bound N to format the string Str +// either to a call to memcpy, or to single character a store, or to nothing, +// and fold the result to a constant. A nonnull StrArg refers to the string +// argument being formatted. Otherwise the call is one with N < 2 and +// the "%c" directive to format a single character. +Value *LibCallSimplifier::emitSnPrintfMemCpy(CallInst *CI, Value *StrArg, + StringRef Str, uint64_t N, + IRBuilderBase &B) { + assert(StrArg || (N < 2 && Str.size() == 1)); + + unsigned IntBits = TLI->getIntSize(); + uint64_t IntMax = maxIntN(IntBits); + if (Str.size() > IntMax) + // Bail if the string is longer than INT_MAX. POSIX requires + // implementations to set errno to EOVERFLOW in this case, in + // addition to when N is larger than that (checked by the caller). + return nullptr; + + Value *StrLen = ConstantInt::get(CI->getType(), Str.size()); + if (N == 0) + return StrLen; + + // Set to the number of bytes to copy fron StrArg which is also + // the offset of the terinating nul. + uint64_t NCopy; + if (N > Str.size()) + // Copy the full string, including the terminating nul (which must + // be present regardless of the bound). + NCopy = Str.size() + 1; + else + NCopy = N - 1; + + Value *DstArg = CI->getArgOperand(0); + if (NCopy && StrArg) + // Transform the call to lvm.memcpy(dst, fmt, N). + copyFlags( + *CI, + B.CreateMemCpy( + DstArg, Align(1), StrArg, Align(1), + ConstantInt::get(DL.getIntPtrType(CI->getContext()), NCopy))); + + if (N > Str.size()) + // Return early when the whole format string, including the final nul, + // has been copied. + return StrLen; + + // Otherwise, when truncating the string append a terminating nul. + Type *Int8Ty = B.getInt8Ty(); + Value *NulOff = B.getIntN(IntBits, NCopy); + Value *DstEnd = B.CreateInBoundsGEP(Int8Ty, DstArg, NulOff, "endptr"); + B.CreateStore(ConstantInt::get(Int8Ty, 0), DstEnd); + return StrLen; +} + Value *LibCallSimplifier::optimizeSnPrintFString(CallInst *CI, IRBuilderBase &B) { // Check for size @@ -2916,78 +2970,66 @@ return nullptr; uint64_t N = Size->getZExtValue(); + uint64_t IntMax = maxIntN(TLI->getIntSize()); + if (N > IntMax) + // Bail if the bound exceeds INT_MAX. POSIX requires implementations + // to set errno to EOVERFLOW in this case. + return nullptr; + + Value *DstArg = CI->getArgOperand(0); + Value *FmtArg = CI->getArgOperand(2); + // Check for a fixed format string. StringRef FormatStr; - if (!getConstantStringInfo(CI->getArgOperand(2), FormatStr)) + if (!getConstantStringInfo(FmtArg, FormatStr)) return nullptr; // If we just have a format string (nothing else crazy) transform it. if (CI->arg_size() == 3) { - // Make sure there's no % in the constant array. We could try to handle - // %% -> % in the future if we cared. if (FormatStr.contains('%')) - return nullptr; // we found a format specifier, bail out. - - if (N == 0) - return ConstantInt::get(CI->getType(), FormatStr.size()); - else if (N < FormatStr.size() + 1) + // Bail if the format string contains a directive and there are + // no arguments. We could handle "%%" in the future. return nullptr; - // snprintf(dst, size, fmt) -> llvm.memcpy(align 1 dst, align 1 fmt, - // strlen(fmt)+1) - copyFlags( - *CI, - B.CreateMemCpy( - CI->getArgOperand(0), Align(1), CI->getArgOperand(2), Align(1), - ConstantInt::get(DL.getIntPtrType(CI->getContext()), - FormatStr.size() + 1))); // Copy the null byte. - return ConstantInt::get(CI->getType(), FormatStr.size()); + return emitSnPrintfMemCpy(CI, FmtArg, FormatStr, N, B); } // The remaining optimizations require the format string to be "%s" or "%c" // and have an extra operand. - if (FormatStr.size() == 2 && FormatStr[0] == '%' && CI->arg_size() == 4) { - - // Decode the second character of the format string. - if (FormatStr[1] == 'c') { - if (N == 0) - return ConstantInt::get(CI->getType(), 1); - else if (N == 1) - return nullptr; - - // snprintf(dst, size, "%c", chr) --> *(i8*)dst = chr; *((i8*)dst+1) = 0 - if (!CI->getArgOperand(3)->getType()->isIntegerTy()) - return nullptr; - Value *V = B.CreateTrunc(CI->getArgOperand(3), B.getInt8Ty(), "char"); - Value *Ptr = castToCStr(CI->getArgOperand(0), B); - B.CreateStore(V, Ptr); - Ptr = B.CreateInBoundsGEP(B.getInt8Ty(), Ptr, B.getInt32(1), "nul"); - B.CreateStore(B.getInt8(0), Ptr); + if (FormatStr.size() != 2 || FormatStr[0] != '%' || CI->arg_size() != 4) + return nullptr; - return ConstantInt::get(CI->getType(), 1); + // Decode the second character of the format string. + if (FormatStr[1] == 'c') { + if (N <= 1) { + // Use an arbitary string of length 1 to transform the call into + // either a nul store (N == 1) or a no-op (N == 0) and fold it + // to one. + StringRef CharStr("*"); + return emitSnPrintfMemCpy(CI, nullptr, CharStr, N, B); } - if (FormatStr[1] == 's') { - // snprintf(dest, size, "%s", str) to llvm.memcpy(dest, str, len+1, 1) - StringRef Str; - if (!getConstantStringInfo(CI->getArgOperand(3), Str)) - return nullptr; + // snprintf(dst, size, "%c", chr) --> *(i8*)dst = chr; *((i8*)dst+1) = 0 + if (!CI->getArgOperand(3)->getType()->isIntegerTy()) + return nullptr; + Value *V = B.CreateTrunc(CI->getArgOperand(3), B.getInt8Ty(), "char"); + Value *Ptr = castToCStr(DstArg, B); + B.CreateStore(V, Ptr); + Ptr = B.CreateInBoundsGEP(B.getInt8Ty(), Ptr, B.getInt32(1), "nul"); + B.CreateStore(B.getInt8(0), Ptr); + return ConstantInt::get(CI->getType(), 1); + } - if (N == 0) - return ConstantInt::get(CI->getType(), Str.size()); - else if (N < Str.size() + 1) - return nullptr; + if (FormatStr[1] != 's') + return nullptr; - copyFlags( - *CI, B.CreateMemCpy(CI->getArgOperand(0), Align(1), - CI->getArgOperand(3), Align(1), - ConstantInt::get(CI->getType(), Str.size() + 1))); + Value *StrArg = CI->getArgOperand(3); + // snprintf(dest, size, "%s", str) to llvm.memcpy(dest, str, len+1, 1) + StringRef Str; + if (!getConstantStringInfo(StrArg, Str)) + return nullptr; - // The snprintf result is the unincremented number of bytes in the string. - return ConstantInt::get(CI->getType(), Str.size()); - } - } - return nullptr; + return emitSnPrintfMemCpy(CI, StrArg, Str, N, B); } Value *LibCallSimplifier::optimizeSnPrintF(CallInst *CI, IRBuilderBase &B) { diff --git a/llvm/test/Transforms/InstCombine/snprintf-2.ll b/llvm/test/Transforms/InstCombine/snprintf-2.ll new file mode 100644 --- /dev/null +++ b/llvm/test/Transforms/InstCombine/snprintf-2.ll @@ -0,0 +1,136 @@ +; NOTE: Assertions have been autogenerated by utils/update_test_checks.py +; +; Verify that snprintf calls with a constant size not exceeding INT_MAX +; and constant format string with no formatting directives are transformed +; into memcpy. Also verify that a size in excess of INT_MAX prevents +; the transformation. +; +; RUN: opt < %s -passes=instcombine -S -data-layout="E" | FileCheck %s -check-prefixes=ANY,BE +; RUN: opt < %s -passes=instcombine -S -data-layout="e" | FileCheck %s -check-prefixes=ANY,LE + +@s = constant [4 x i8] c"123\00" + +@adst = external global [0 x i8*] +@asiz = external global [0 x i32] + +declare i32 @snprintf(i8*, i64, i8*, ...) + + +; Verify that all snprintf calls with a bound between INT_MAX and down +; to 0 are transformed to memcpy. + +define void @fold_snprintf_fmt() { +; BE-LABEL: @fold_snprintf_fmt( +; BE-NEXT: [[PDIMAX1:%.*]] = load i32*, i32** bitcast (i8** getelementptr inbounds ([0 x i8*], [0 x i8*]* @adst, i64 0, i64 2147483647) to i32**), align 8 +; BE-NEXT: store i32 825373440, i32* [[PDIMAX1]], align 1 +; BE-NEXT: store i32 3, i32* getelementptr inbounds ([0 x i32], [0 x i32]* @asiz, i64 0, i64 0), align 4 +; BE-NEXT: [[PD52:%.*]] = load i32*, i32** bitcast (i8** getelementptr inbounds ([0 x i8*], [0 x i8*]* @adst, i64 0, i64 5) to i32**), align 8 +; BE-NEXT: store i32 825373440, i32* [[PD52]], align 1 +; BE-NEXT: store i32 3, i32* getelementptr inbounds ([0 x i32], [0 x i32]* @asiz, i64 0, i64 5), align 4 +; BE-NEXT: [[PD43:%.*]] = load i32*, i32** bitcast (i8** getelementptr inbounds ([0 x i8*], [0 x i8*]* @adst, i64 0, i64 4) to i32**), align 8 +; BE-NEXT: store i32 825373440, i32* [[PD43]], align 1 +; BE-NEXT: store i32 3, i32* getelementptr inbounds ([0 x i32], [0 x i32]* @asiz, i64 0, i64 4), align 4 +; BE-NEXT: [[PD3:%.*]] = load i8*, i8** getelementptr inbounds ([0 x i8*], [0 x i8*]* @adst, i64 0, i64 3), align 8 +; BE-NEXT: [[TMP1:%.*]] = bitcast i8* [[PD3]] to i16* +; BE-NEXT: store i16 12594, i16* [[TMP1]], align 1 +; BE-NEXT: [[ENDPTR:%.*]] = getelementptr inbounds i8, i8* [[PD3]], i64 2 +; BE-NEXT: store i8 0, i8* [[ENDPTR]], align 1 +; BE-NEXT: store i32 3, i32* getelementptr inbounds ([0 x i32], [0 x i32]* @asiz, i64 0, i64 3), align 4 +; BE-NEXT: [[PD2:%.*]] = load i8*, i8** getelementptr inbounds ([0 x i8*], [0 x i8*]* @adst, i64 0, i64 2), align 8 +; BE-NEXT: store i8 49, i8* [[PD2]], align 1 +; BE-NEXT: [[ENDPTR4:%.*]] = getelementptr inbounds i8, i8* [[PD2]], i64 1 +; BE-NEXT: store i8 0, i8* [[ENDPTR4]], align 1 +; BE-NEXT: store i32 3, i32* getelementptr inbounds ([0 x i32], [0 x i32]* @asiz, i64 0, i64 2), align 4 +; BE-NEXT: [[PD1:%.*]] = load i8*, i8** getelementptr inbounds ([0 x i8*], [0 x i8*]* @adst, i64 0, i64 1), align 8 +; BE-NEXT: store i8 0, i8* [[PD1]], align 1 +; BE-NEXT: store i32 3, i32* getelementptr inbounds ([0 x i32], [0 x i32]* @asiz, i64 0, i64 1), align 4 +; BE-NEXT: store i32 3, i32* getelementptr inbounds ([0 x i32], [0 x i32]* @asiz, i64 0, i64 0), align 4 +; BE-NEXT: ret void +; +; LE-LABEL: @fold_snprintf_fmt( +; LE-NEXT: [[PDIMAX1:%.*]] = load i32*, i32** bitcast (i8** getelementptr inbounds ([0 x i8*], [0 x i8*]* @adst, i64 0, i64 2147483647) to i32**), align 8 +; LE-NEXT: store i32 3355185, i32* [[PDIMAX1]], align 1 +; LE-NEXT: store i32 3, i32* getelementptr inbounds ([0 x i32], [0 x i32]* @asiz, i64 0, i64 0), align 4 +; LE-NEXT: [[PD52:%.*]] = load i32*, i32** bitcast (i8** getelementptr inbounds ([0 x i8*], [0 x i8*]* @adst, i64 0, i64 5) to i32**), align 8 +; LE-NEXT: store i32 3355185, i32* [[PD52]], align 1 +; LE-NEXT: store i32 3, i32* getelementptr inbounds ([0 x i32], [0 x i32]* @asiz, i64 0, i64 5), align 4 +; LE-NEXT: [[PD43:%.*]] = load i32*, i32** bitcast (i8** getelementptr inbounds ([0 x i8*], [0 x i8*]* @adst, i64 0, i64 4) to i32**), align 8 +; LE-NEXT: store i32 3355185, i32* [[PD43]], align 1 +; LE-NEXT: store i32 3, i32* getelementptr inbounds ([0 x i32], [0 x i32]* @asiz, i64 0, i64 4), align 4 +; LE-NEXT: [[PD3:%.*]] = load i8*, i8** getelementptr inbounds ([0 x i8*], [0 x i8*]* @adst, i64 0, i64 3), align 8 +; LE-NEXT: [[TMP1:%.*]] = bitcast i8* [[PD3]] to i16* +; LE-NEXT: store i16 12849, i16* [[TMP1]], align 1 +; LE-NEXT: [[ENDPTR:%.*]] = getelementptr inbounds i8, i8* [[PD3]], i64 2 +; LE-NEXT: store i8 0, i8* [[ENDPTR]], align 1 +; LE-NEXT: store i32 3, i32* getelementptr inbounds ([0 x i32], [0 x i32]* @asiz, i64 0, i64 3), align 4 +; LE-NEXT: [[PD2:%.*]] = load i8*, i8** getelementptr inbounds ([0 x i8*], [0 x i8*]* @adst, i64 0, i64 2), align 8 +; LE-NEXT: store i8 49, i8* [[PD2]], align 1 +; LE-NEXT: [[ENDPTR4:%.*]] = getelementptr inbounds i8, i8* [[PD2]], i64 1 +; LE-NEXT: store i8 0, i8* [[ENDPTR4]], align 1 +; LE-NEXT: store i32 3, i32* getelementptr inbounds ([0 x i32], [0 x i32]* @asiz, i64 0, i64 2), align 4 +; LE-NEXT: [[PD1:%.*]] = load i8*, i8** getelementptr inbounds ([0 x i8*], [0 x i8*]* @adst, i64 0, i64 1), align 8 +; LE-NEXT: store i8 0, i8* [[PD1]], align 1 +; LE-NEXT: store i32 3, i32* getelementptr inbounds ([0 x i32], [0 x i32]* @asiz, i64 0, i64 1), align 4 +; LE-NEXT: store i32 3, i32* getelementptr inbounds ([0 x i32], [0 x i32]* @asiz, i64 0, i64 0), align 4 +; LE-NEXT: ret void +; + %fmt = getelementptr [4 x i8], [4 x i8]* @s, i32 0, i32 0 + + %pdimax = load i8*, i8** getelementptr ([0 x i8*], [0 x i8*]* @adst, i32 0, i32 2147483647) + %nimax = call i32 (i8*, i64, i8*, ...) @snprintf(i8* %pdimax, i64 2147483647, i8* %fmt) + store i32 %nimax, i32* getelementptr ([0 x i32], [0 x i32]* @asiz, i32 0, i32 0) + + %pd5 = load i8*, i8** getelementptr ([0 x i8*], [0 x i8*]* @adst, i32 0, i32 5) + %n5 = call i32 (i8*, i64, i8*, ...) @snprintf(i8* %pd5, i64 5, i8* %fmt) + store i32 %n5, i32* getelementptr ([0 x i32], [0 x i32]* @asiz, i32 0, i32 5) + + %pd4 = load i8*, i8** getelementptr ([0 x i8*], [0 x i8*]* @adst, i32 0, i32 4) + %n4 = call i32 (i8*, i64, i8*, ...) @snprintf(i8* %pd4, i64 4, i8* %fmt) + store i32 %n4, i32* getelementptr ([0 x i32], [0 x i32]* @asiz, i32 0, i32 4) + + %pd3 = load i8*, i8** getelementptr ([0 x i8*], [0 x i8*]* @adst, i32 0, i32 3) + %n3 = call i32 (i8*, i64, i8*, ...) @snprintf(i8* %pd3, i64 3, i8* %fmt) + store i32 %n3, i32* getelementptr ([0 x i32], [0 x i32]* @asiz, i32 0, i32 3) + + %pd2 = load i8*, i8** getelementptr ([0 x i8*], [0 x i8*]* @adst, i32 0, i32 2) + %n2 = call i32 (i8*, i64, i8*, ...) @snprintf(i8* %pd2, i64 2, i8* %fmt) + store i32 %n2, i32* getelementptr ([0 x i32], [0 x i32]* @asiz, i32 0, i32 2) + + %pd1 = load i8*, i8** getelementptr ([0 x i8*], [0 x i8*]* @adst, i32 0, i32 1) + %n1 = call i32 (i8*, i64, i8*, ...) @snprintf(i8* %pd1, i64 1, i8* %fmt) + store i32 %n1, i32* getelementptr ([0 x i32], [0 x i32]* @asiz, i32 0, i32 1) + + %pd0 = load i8*, i8** getelementptr ([0 x i8*], [0 x i8*]* @adst, i32 0, i32 0) + %n0 = call i32 (i8*, i64, i8*, ...) @snprintf(i8* %pd0, i64 0, i8* %fmt) + store i32 %n0, i32* getelementptr ([0 x i32], [0 x i32]* @asiz, i32 0, i32 0) + + ret void +} + + +; Verify that snprintf calls with a bound greater than INT_MAX are not +; transformed. POSIX requires implementations to set errno to EOVERFLOW +; so such calls could be folded to just that followed by returning -1. + +define void @call_snprintf_fmt_ximax() { +; ANY-LABEL: @call_snprintf_fmt_ximax( +; ANY-NEXT: [[PDM1:%.*]] = load i8*, i8** getelementptr inbounds ([0 x i8*], [0 x i8*]* @adst, i64 0, i64 1), align 8 +; ANY-NEXT: [[NM1:%.*]] = call i32 (i8*, i64, i8*, ...) @snprintf(i8* noundef nonnull dereferenceable(1) [[PDM1]], i64 -1, i8* getelementptr inbounds ([4 x i8], [4 x i8]* @s, i64 0, i64 0)) +; ANY-NEXT: store i32 [[NM1]], i32* getelementptr inbounds ([0 x i32], [0 x i32]* @asiz, i64 0, i64 1), align 4 +; ANY-NEXT: [[PDIMAXP1:%.*]] = load i8*, i8** getelementptr inbounds ([0 x i8*], [0 x i8*]* @adst, i64 0, i64 0), align 8 +; ANY-NEXT: [[NIMAXP1:%.*]] = call i32 (i8*, i64, i8*, ...) @snprintf(i8* noundef nonnull dereferenceable(1) [[PDIMAXP1]], i64 2147483648, i8* getelementptr inbounds ([4 x i8], [4 x i8]* @s, i64 0, i64 0)) +; ANY-NEXT: store i32 [[NIMAXP1]], i32* getelementptr inbounds ([0 x i32], [0 x i32]* @asiz, i64 0, i64 0), align 4 +; ANY-NEXT: ret void +; + %fmt = getelementptr [4 x i8], [4 x i8]* @s, i32 0, i32 0 + + %pdm1 = load i8*, i8** getelementptr ([0 x i8*], [0 x i8*]* @adst, i32 0, i32 1) + %nm1 = call i32 (i8*, i64, i8*, ...) @snprintf(i8* %pdm1, i64 -1, i8* %fmt) + store i32 %nm1, i32* getelementptr ([0 x i32], [0 x i32]* @asiz, i32 0, i32 1) + + %pdimaxp1 = load i8*, i8** getelementptr ([0 x i8*], [0 x i8*]* @adst, i32 0, i32 0) + %nimaxp1 = call i32 (i8*, i64, i8*, ...) @snprintf(i8* %pdimaxp1, i64 2147483648, i8* %fmt) + store i32 %nimaxp1, i32* getelementptr ([0 x i32], [0 x i32]* @asiz, i32 0, i32 0) + + ret void +} diff --git a/llvm/test/Transforms/InstCombine/snprintf-3.ll b/llvm/test/Transforms/InstCombine/snprintf-3.ll new file mode 100644 --- /dev/null +++ b/llvm/test/Transforms/InstCombine/snprintf-3.ll @@ -0,0 +1,139 @@ +; NOTE: Assertions have been autogenerated by utils/update_test_checks.py +; +; Verify that snprintf calls with a constant size not exceeding INT_MAX +; and a "%s" format string and a const string argument are transformed +; into memcpy. Also verify that a size in excess of INT_MAX prevents +; the transformation. +; +; RUN: opt < %s -passes=instcombine -S -data-layout="E" | FileCheck %s -check-prefixes=ANY,BE +; RUN: opt < %s -passes=instcombine -S -data-layout="e" | FileCheck %s -check-prefixes=ANY,LE + +@pcnt_s = constant [3 x i8] c"%s\00" +@s = constant [4 x i8] c"123\00" + +@adst = external global [0 x i8*] +@asiz = external global [0 x i32] + +declare i32 @snprintf(i8*, i64, i8*, ...) + + +; Verify that all snprintf calls with a bound between INT_MAX and down +; to 0 are transformed to memcpy. + +define void @fold_snprintf_pcnt_s() { +; BE-LABEL: @fold_snprintf_pcnt_s( +; BE-NEXT: [[PDIMAX1:%.*]] = load i32*, i32** bitcast (i8** getelementptr inbounds ([0 x i8*], [0 x i8*]* @adst, i64 0, i64 2147483647) to i32**), align 8 +; BE-NEXT: store i32 825373440, i32* [[PDIMAX1]], align 1 +; BE-NEXT: store i32 3, i32* getelementptr inbounds ([0 x i32], [0 x i32]* @asiz, i64 0, i64 0), align 4 +; BE-NEXT: [[PD52:%.*]] = load i32*, i32** bitcast (i8** getelementptr inbounds ([0 x i8*], [0 x i8*]* @adst, i64 0, i64 5) to i32**), align 8 +; BE-NEXT: store i32 825373440, i32* [[PD52]], align 1 +; BE-NEXT: store i32 3, i32* getelementptr inbounds ([0 x i32], [0 x i32]* @asiz, i64 0, i64 5), align 4 +; BE-NEXT: [[PD43:%.*]] = load i32*, i32** bitcast (i8** getelementptr inbounds ([0 x i8*], [0 x i8*]* @adst, i64 0, i64 4) to i32**), align 8 +; BE-NEXT: store i32 825373440, i32* [[PD43]], align 1 +; BE-NEXT: store i32 3, i32* getelementptr inbounds ([0 x i32], [0 x i32]* @asiz, i64 0, i64 4), align 4 +; BE-NEXT: [[PD3:%.*]] = load i8*, i8** getelementptr inbounds ([0 x i8*], [0 x i8*]* @adst, i64 0, i64 3), align 8 +; BE-NEXT: [[TMP1:%.*]] = bitcast i8* [[PD3]] to i16* +; BE-NEXT: store i16 12594, i16* [[TMP1]], align 1 +; BE-NEXT: [[ENDPTR:%.*]] = getelementptr inbounds i8, i8* [[PD3]], i64 2 +; BE-NEXT: store i8 0, i8* [[ENDPTR]], align 1 +; BE-NEXT: store i32 3, i32* getelementptr inbounds ([0 x i32], [0 x i32]* @asiz, i64 0, i64 3), align 4 +; BE-NEXT: [[PD2:%.*]] = load i8*, i8** getelementptr inbounds ([0 x i8*], [0 x i8*]* @adst, i64 0, i64 2), align 8 +; BE-NEXT: store i8 49, i8* [[PD2]], align 1 +; BE-NEXT: [[ENDPTR4:%.*]] = getelementptr inbounds i8, i8* [[PD2]], i64 1 +; BE-NEXT: store i8 0, i8* [[ENDPTR4]], align 1 +; BE-NEXT: store i32 3, i32* getelementptr inbounds ([0 x i32], [0 x i32]* @asiz, i64 0, i64 2), align 4 +; BE-NEXT: [[PD1:%.*]] = load i8*, i8** getelementptr inbounds ([0 x i8*], [0 x i8*]* @adst, i64 0, i64 1), align 8 +; BE-NEXT: store i8 0, i8* [[PD1]], align 1 +; BE-NEXT: store i32 3, i32* getelementptr inbounds ([0 x i32], [0 x i32]* @asiz, i64 0, i64 1), align 4 +; BE-NEXT: store i32 3, i32* getelementptr inbounds ([0 x i32], [0 x i32]* @asiz, i64 0, i64 0), align 4 +; BE-NEXT: ret void +; +; LE-LABEL: @fold_snprintf_pcnt_s( +; LE-NEXT: [[PDIMAX1:%.*]] = load i32*, i32** bitcast (i8** getelementptr inbounds ([0 x i8*], [0 x i8*]* @adst, i64 0, i64 2147483647) to i32**), align 8 +; LE-NEXT: store i32 3355185, i32* [[PDIMAX1]], align 1 +; LE-NEXT: store i32 3, i32* getelementptr inbounds ([0 x i32], [0 x i32]* @asiz, i64 0, i64 0), align 4 +; LE-NEXT: [[PD52:%.*]] = load i32*, i32** bitcast (i8** getelementptr inbounds ([0 x i8*], [0 x i8*]* @adst, i64 0, i64 5) to i32**), align 8 +; LE-NEXT: store i32 3355185, i32* [[PD52]], align 1 +; LE-NEXT: store i32 3, i32* getelementptr inbounds ([0 x i32], [0 x i32]* @asiz, i64 0, i64 5), align 4 +; LE-NEXT: [[PD43:%.*]] = load i32*, i32** bitcast (i8** getelementptr inbounds ([0 x i8*], [0 x i8*]* @adst, i64 0, i64 4) to i32**), align 8 +; LE-NEXT: store i32 3355185, i32* [[PD43]], align 1 +; LE-NEXT: store i32 3, i32* getelementptr inbounds ([0 x i32], [0 x i32]* @asiz, i64 0, i64 4), align 4 +; LE-NEXT: [[PD3:%.*]] = load i8*, i8** getelementptr inbounds ([0 x i8*], [0 x i8*]* @adst, i64 0, i64 3), align 8 +; LE-NEXT: [[TMP1:%.*]] = bitcast i8* [[PD3]] to i16* +; LE-NEXT: store i16 12849, i16* [[TMP1]], align 1 +; LE-NEXT: [[ENDPTR:%.*]] = getelementptr inbounds i8, i8* [[PD3]], i64 2 +; LE-NEXT: store i8 0, i8* [[ENDPTR]], align 1 +; LE-NEXT: store i32 3, i32* getelementptr inbounds ([0 x i32], [0 x i32]* @asiz, i64 0, i64 3), align 4 +; LE-NEXT: [[PD2:%.*]] = load i8*, i8** getelementptr inbounds ([0 x i8*], [0 x i8*]* @adst, i64 0, i64 2), align 8 +; LE-NEXT: store i8 49, i8* [[PD2]], align 1 +; LE-NEXT: [[ENDPTR4:%.*]] = getelementptr inbounds i8, i8* [[PD2]], i64 1 +; LE-NEXT: store i8 0, i8* [[ENDPTR4]], align 1 +; LE-NEXT: store i32 3, i32* getelementptr inbounds ([0 x i32], [0 x i32]* @asiz, i64 0, i64 2), align 4 +; LE-NEXT: [[PD1:%.*]] = load i8*, i8** getelementptr inbounds ([0 x i8*], [0 x i8*]* @adst, i64 0, i64 1), align 8 +; LE-NEXT: store i8 0, i8* [[PD1]], align 1 +; LE-NEXT: store i32 3, i32* getelementptr inbounds ([0 x i32], [0 x i32]* @asiz, i64 0, i64 1), align 4 +; LE-NEXT: store i32 3, i32* getelementptr inbounds ([0 x i32], [0 x i32]* @asiz, i64 0, i64 0), align 4 +; LE-NEXT: ret void +; + %fmt = getelementptr [3 x i8], [3 x i8]* @pcnt_s, i32 0, i32 0 + %ps = getelementptr [4 x i8], [4 x i8]* @s, i32 0, i32 0 + + %pdimax = load i8*, i8** getelementptr ([0 x i8*], [0 x i8*]* @adst, i32 0, i32 2147483647) + %nimax = call i32 (i8*, i64, i8*, ...) @snprintf(i8* %pdimax, i64 2147483647, i8* %fmt, i8* %ps) + store i32 %nimax, i32* getelementptr ([0 x i32], [0 x i32]* @asiz, i32 0, i32 0) + + %pd5 = load i8*, i8** getelementptr ([0 x i8*], [0 x i8*]* @adst, i32 0, i32 5) + %n5 = call i32 (i8*, i64, i8*, ...) @snprintf(i8* %pd5, i64 5, i8* %fmt, i8* %ps) + store i32 %n5, i32* getelementptr ([0 x i32], [0 x i32]* @asiz, i32 0, i32 5) + + %pd4 = load i8*, i8** getelementptr ([0 x i8*], [0 x i8*]* @adst, i32 0, i32 4) + %n4 = call i32 (i8*, i64, i8*, ...) @snprintf(i8* %pd4, i64 4, i8* %fmt, i8* %ps) + store i32 %n4, i32* getelementptr ([0 x i32], [0 x i32]* @asiz, i32 0, i32 4) + + %pd3 = load i8*, i8** getelementptr ([0 x i8*], [0 x i8*]* @adst, i32 0, i32 3) + %n3 = call i32 (i8*, i64, i8*, ...) @snprintf(i8* %pd3, i64 3, i8* %fmt, i8* %ps) + store i32 %n3, i32* getelementptr ([0 x i32], [0 x i32]* @asiz, i32 0, i32 3) + + %pd2 = load i8*, i8** getelementptr ([0 x i8*], [0 x i8*]* @adst, i32 0, i32 2) + %n2 = call i32 (i8*, i64, i8*, ...) @snprintf(i8* %pd2, i64 2, i8* %fmt, i8* %ps) + store i32 %n2, i32* getelementptr ([0 x i32], [0 x i32]* @asiz, i32 0, i32 2) + + %pd1 = load i8*, i8** getelementptr ([0 x i8*], [0 x i8*]* @adst, i32 0, i32 1) + %n1 = call i32 (i8*, i64, i8*, ...) @snprintf(i8* %pd1, i64 1, i8* %fmt, i8* %ps) + store i32 %n1, i32* getelementptr ([0 x i32], [0 x i32]* @asiz, i32 0, i32 1) + + %pd0 = load i8*, i8** getelementptr ([0 x i8*], [0 x i8*]* @adst, i32 0, i32 0) + %n0 = call i32 (i8*, i64, i8*, ...) @snprintf(i8* %pd0, i64 0, i8* %fmt, i8* %ps) + store i32 %n0, i32* getelementptr ([0 x i32], [0 x i32]* @asiz, i32 0, i32 0) + + ret void +} + + +; Verify that snprintf calls with a bound greater than INT_MAX are not +; transformed. POSIX requires implementations to set errno to EOVERFLOW +; so such calls could be folded to just that followed by returning -1. + +define void @call_snprintf_pcnt_s_ximax() { +; ANY-LABEL: @call_snprintf_pcnt_s_ximax( +; ANY-NEXT: [[PDM1:%.*]] = load i8*, i8** getelementptr inbounds ([0 x i8*], [0 x i8*]* @adst, i64 0, i64 1), align 8 +; ANY-NEXT: [[NM1:%.*]] = call i32 (i8*, i64, i8*, ...) @snprintf(i8* noundef nonnull dereferenceable(1) [[PDM1]], i64 -1, i8* getelementptr inbounds ([3 x i8], [3 x i8]* @pcnt_s, i64 0, i64 0), i8* getelementptr inbounds ([4 x i8], [4 x i8]* @s, i64 0, i64 0)) +; ANY-NEXT: store i32 [[NM1]], i32* getelementptr inbounds ([0 x i32], [0 x i32]* @asiz, i64 0, i64 1), align 4 +; ANY-NEXT: [[PDIMAXP1:%.*]] = load i8*, i8** getelementptr inbounds ([0 x i8*], [0 x i8*]* @adst, i64 0, i64 0), align 8 +; ANY-NEXT: [[NIMAXP1:%.*]] = call i32 (i8*, i64, i8*, ...) @snprintf(i8* noundef nonnull dereferenceable(1) [[PDIMAXP1]], i64 2147483648, i8* getelementptr inbounds ([3 x i8], [3 x i8]* @pcnt_s, i64 0, i64 0), i8* getelementptr inbounds ([4 x i8], [4 x i8]* @s, i64 0, i64 0)) +; ANY-NEXT: store i32 [[NIMAXP1]], i32* getelementptr inbounds ([0 x i32], [0 x i32]* @asiz, i64 0, i64 0), align 4 +; ANY-NEXT: ret void +; + %fmt = getelementptr [3 x i8], [3 x i8]* @pcnt_s, i32 0, i32 0 + %ps = getelementptr [4 x i8], [4 x i8]* @s, i32 0, i32 0 + + %pdm1 = load i8*, i8** getelementptr ([0 x i8*], [0 x i8*]* @adst, i32 0, i32 1) + %nm1 = call i32 (i8*, i64, i8*, ...) @snprintf(i8* %pdm1, i64 -1, i8* %fmt, i8* %ps) + store i32 %nm1, i32* getelementptr ([0 x i32], [0 x i32]* @asiz, i32 0, i32 1) + + %pdimaxp1 = load i8*, i8** getelementptr ([0 x i8*], [0 x i8*]* @adst, i32 0, i32 0) + %nimaxp1 = call i32 (i8*, i64, i8*, ...) @snprintf(i8* %pdimaxp1, i64 2147483648, i8* %fmt, i8* %ps) + store i32 %nimaxp1, i32* getelementptr ([0 x i32], [0 x i32]* @asiz, i32 0, i32 0) + + ret void +} diff --git a/llvm/test/Transforms/InstCombine/snprintf-4.ll b/llvm/test/Transforms/InstCombine/snprintf-4.ll new file mode 100644 --- /dev/null +++ b/llvm/test/Transforms/InstCombine/snprintf-4.ll @@ -0,0 +1,129 @@ +; NOTE: Assertions have been autogenerated by utils/update_test_checks.py +; +; Verify that snprintf calls with a constant size not exceeding INT_MAX +; and a "%c" format string are transformed into a store of the character. +; Also verify that a size in excess of INT_MAX prevents the transformation. +; +; RUN: opt < %s -passes=instcombine -S | FileCheck %s + +@pcnt_c = constant [3 x i8] c"%c\00" + +@adst = external global [0 x i8*] +@asiz = external global [0 x i32] + +declare i32 @snprintf(i8*, i64, i8*, ...) + + +; Verify that all snprintf calls with a bound between INT_MAX and down +; to 0 are transformed to memcpy. + +define void @fold_snprintf_pcnt_c(i32 %c) { +; CHECK-LABEL: @fold_snprintf_pcnt_c( +; CHECK-NEXT: [[PDIMAX:%.*]] = load i8*, i8** getelementptr inbounds ([0 x i8*], [0 x i8*]* @adst, i64 0, i64 0), align 8 +; CHECK-NEXT: store i8 1, i8* [[PDIMAX]], align 1 +; CHECK-NEXT: [[NUL:%.*]] = getelementptr inbounds i8, i8* [[PDIMAX]], i64 1 +; CHECK-NEXT: store i8 0, i8* [[NUL]], align 1 +; CHECK-NEXT: store i32 1, i32* getelementptr inbounds ([0 x i32], [0 x i32]* @asiz, i64 0, i64 0), align 4 +; CHECK-NEXT: [[PD2:%.*]] = load i8*, i8** getelementptr inbounds ([0 x i8*], [0 x i8*]* @adst, i64 0, i64 1), align 8 +; CHECK-NEXT: store i8 2, i8* [[PD2]], align 1 +; CHECK-NEXT: [[NUL1:%.*]] = getelementptr inbounds i8, i8* [[PD2]], i64 1 +; CHECK-NEXT: store i8 0, i8* [[NUL1]], align 1 +; CHECK-NEXT: store i32 1, i32* getelementptr inbounds ([0 x i32], [0 x i32]* @asiz, i64 0, i64 1), align 4 +; CHECK-NEXT: [[PD2_0:%.*]] = load i8*, i8** getelementptr inbounds ([0 x i8*], [0 x i8*]* @adst, i64 0, i64 2), align 8 +; CHECK-NEXT: store i8 0, i8* [[PD2_0]], align 1 +; CHECK-NEXT: [[NUL2:%.*]] = getelementptr inbounds i8, i8* [[PD2_0]], i64 1 +; CHECK-NEXT: store i8 0, i8* [[NUL2]], align 1 +; CHECK-NEXT: store i32 1, i32* getelementptr inbounds ([0 x i32], [0 x i32]* @asiz, i64 0, i64 2), align 4 +; CHECK-NEXT: [[PD1:%.*]] = load i8*, i8** getelementptr inbounds ([0 x i8*], [0 x i8*]* @adst, i64 0, i64 3), align 8 +; CHECK-NEXT: store i8 0, i8* [[PD1]], align 1 +; CHECK-NEXT: store i32 1, i32* getelementptr inbounds ([0 x i32], [0 x i32]* @asiz, i64 0, i64 3), align 4 +; CHECK-NEXT: store i32 1, i32* getelementptr inbounds ([0 x i32], [0 x i32]* @asiz, i64 0, i64 4), align 4 +; CHECK-NEXT: [[PD2_C:%.*]] = load i8*, i8** getelementptr inbounds ([0 x i8*], [0 x i8*]* @adst, i64 0, i64 4), align 8 +; CHECK-NEXT: [[CHAR:%.*]] = trunc i32 [[C:%.*]] to i8 +; CHECK-NEXT: store i8 [[CHAR]], i8* [[PD2_C]], align 1 +; CHECK-NEXT: [[NUL3:%.*]] = getelementptr inbounds i8, i8* [[PD2_C]], i64 1 +; CHECK-NEXT: store i8 0, i8* [[NUL3]], align 1 +; CHECK-NEXT: store i32 1, i32* getelementptr inbounds ([0 x i32], [0 x i32]* @asiz, i64 0, i64 4), align 4 +; CHECK-NEXT: [[PD1_C:%.*]] = load i8*, i8** getelementptr inbounds ([0 x i8*], [0 x i8*]* @adst, i64 0, i64 5), align 8 +; CHECK-NEXT: store i8 0, i8* [[PD1_C]], align 1 +; CHECK-NEXT: store i32 1, i32* getelementptr inbounds ([0 x i32], [0 x i32]* @asiz, i64 0, i64 5), align 4 +; CHECK-NEXT: ret void +; + %fmt = getelementptr [3 x i8], [3 x i8]* @pcnt_c, i32 0, i32 0 + + ; Transform snprintf(dst, INT_MAX, "%c", 1) to memcpy(dst, "1", 2), 1. + %pdimax = load i8*, i8** getelementptr ([0 x i8*], [0 x i8*]* @adst, i32 0, i32 0) + %nimax = call i32 (i8*, i64, i8*, ...) @snprintf(i8* %pdimax, i64 2147483647, i8* %fmt, i32 1) + store i32 %nimax, i32* getelementptr ([0 x i32], [0 x i32]* @asiz, i32 0, i32 0) + + ; Transform snprintf(dst, 2, "%c", '\2') to memcpy(dst, "2", 2), 1. + %pd2 = load i8*, i8** getelementptr ([0 x i8*], [0 x i8*]* @adst, i32 0, i32 1) + %n2 = call i32 (i8*, i64, i8*, ...) @snprintf(i8* %pd2, i64 2, i8* %fmt, i8 2) + store i32 %n2, i32* getelementptr ([0 x i32], [0 x i32]* @asiz, i32 0, i32 1) + + ; Transform snprintf(dst, 2, "%c", '\0') to memcpy(dst, "\0", 2), 1. + %pd2_0 = load i8*, i8** getelementptr ([0 x i8*], [0 x i8*]* @adst, i32 0, i32 2) + %n2_0 = call i32 (i8*, i64, i8*, ...) @snprintf(i8* %pd2_0, i64 2, i8* %fmt, i8 0) + store i32 %n2_0, i32* getelementptr ([0 x i32], [0 x i32]* @asiz, i32 0, i32 2) + + ; Transform snprintf(dst, 1, "%c", (short)3) to memcpy(dst, "\3", 2), 1. + %pd1 = load i8*, i8** getelementptr ([0 x i8*], [0 x i8*]* @adst, i32 0, i32 3) + %n1 = call i32 (i8*, i64, i8*, ...) @snprintf(i8* %pd1, i64 1, i8* %fmt, i16 3) + store i32 %n1, i32* getelementptr ([0 x i32], [0 x i32]* @asiz, i32 0, i32 3) + + ; Fold snprintf(dst, 0, "%c", 4) to 1. + %pd0 = load i8*, i8** getelementptr ([0 x i8*], [0 x i8*]* @adst, i32 0, i32 4) + %n0 = call i32 (i8*, i64, i8*, ...) @snprintf(i8* %pd0, i64 0, i8* %fmt, i32 4) + store i32 %n0, i32* getelementptr ([0 x i32], [0 x i32]* @asiz, i32 0, i32 4) + + + ; Transform snprintf(dst, 2, "%c", c) with a nonconstant c to + ; dst[0] = c, dst[1] = '\0', 1. + %pd2_c = load i8*, i8** getelementptr ([0 x i8*], [0 x i8*]* @adst, i32 0, i32 4) + %n2_c = call i32 (i8*, i64, i8*, ...) @snprintf(i8* %pd2_c, i64 2, i8* %fmt, i32 %c) + store i32 %n2_c, i32* getelementptr ([0 x i32], [0 x i32]* @asiz, i32 0, i32 4) + + ; Transform snprintf(dst, 1, "%c", c) with a nonconstant c to *dst = '\0', 0. + %pd1_c = load i8*, i8** getelementptr ([0 x i8*], [0 x i8*]* @adst, i32 0, i32 5) + %n1_c = call i32 (i8*, i64, i8*, ...) @snprintf(i8* %pd1_c, i64 1, i8* %fmt, i32 %c) + store i32 %n1_c, i32* getelementptr ([0 x i32], [0 x i32]* @asiz, i32 0, i32 5) + + ret void +} + + +; Verify that snprintf calls with a bound greater than INT_MAX are not +; transformed. POSIX requires implementations to set errno to EOVERFLOW +; so such calls could be folded to just that followed by returning -1. + +define void @call_snprintf_pcnt_c_ximax(i32 %c) { +; CHECK-LABEL: @call_snprintf_pcnt_c_ximax( +; CHECK-NEXT: [[PDM1:%.*]] = load i8*, i8** getelementptr inbounds ([0 x i8*], [0 x i8*]* @adst, i64 0, i64 0), align 8 +; CHECK-NEXT: [[NM1:%.*]] = call i32 (i8*, i64, i8*, ...) @snprintf(i8* noundef nonnull dereferenceable(1) [[PDM1]], i64 -1, i8* getelementptr inbounds ([3 x i8], [3 x i8]* @pcnt_c, i64 0, i64 0), i8 0) +; CHECK-NEXT: store i32 [[NM1]], i32* getelementptr inbounds ([0 x i32], [0 x i32]* @asiz, i64 0, i64 0), align 4 +; CHECK-NEXT: [[PDIMAXP1:%.*]] = load i8*, i8** getelementptr inbounds ([0 x i8*], [0 x i8*]* @adst, i64 0, i64 1), align 8 +; CHECK-NEXT: [[NIMAXP1:%.*]] = call i32 (i8*, i64, i8*, ...) @snprintf(i8* noundef nonnull dereferenceable(1) [[PDIMAXP1]], i64 2147483648, i8* getelementptr inbounds ([3 x i8], [3 x i8]* @pcnt_c, i64 0, i64 0), i8 1) +; CHECK-NEXT: store i32 [[NIMAXP1]], i32* getelementptr inbounds ([0 x i32], [0 x i32]* @asiz, i64 0, i64 1), align 4 +; CHECK-NEXT: [[PDM1SL32:%.*]] = load i8*, i8** getelementptr inbounds ([0 x i8*], [0 x i8*]* @adst, i64 0, i64 2), align 8 +; CHECK-NEXT: [[NM1SL32:%.*]] = call i32 (i8*, i64, i8*, ...) @snprintf(i8* noundef nonnull dereferenceable(1) [[PDM1SL32]], i64 -4294967296, i8* getelementptr inbounds ([3 x i8], [3 x i8]* @pcnt_c, i64 0, i64 0), i8 1) +; CHECK-NEXT: store i32 [[NM1SL32]], i32* getelementptr inbounds ([0 x i32], [0 x i32]* @asiz, i64 0, i64 2), align 4 +; CHECK-NEXT: ret void +; + %fmt = getelementptr [3 x i8], [3 x i8]* @pcnt_c, i32 0, i32 0 + + %pdm1 = load i8*, i8** getelementptr ([0 x i8*], [0 x i8*]* @adst, i32 0, i32 0) + %nm1 = call i32 (i8*, i64, i8*, ...) @snprintf(i8* %pdm1, i64 -1, i8* %fmt, i8 0) + store i32 %nm1, i32* getelementptr ([0 x i32], [0 x i32]* @asiz, i32 0, i32 0) + + + %pdimaxp1 = load i8*, i8** getelementptr ([0 x i8*], [0 x i8*]* @adst, i32 0, i32 1) + %nimaxp1 = call i32 (i8*, i64, i8*, ...) @snprintf(i8* %pdimaxp1, i64 2147483648, i8* %fmt, i8 1) + store i32 %nimaxp1, i32* getelementptr ([0 x i32], [0 x i32]* @asiz, i32 0, i32 1) + + ; Exercise snprintf(dst, -1LU << 32, "%c", c). + %pdm1sl32 = load i8*, i8** getelementptr ([0 x i8*], [0 x i8*]* @adst, i32 0, i32 2) + %nm1sl32 = call i32 (i8*, i64, i8*, ...) @snprintf(i8* %pdm1sl32, i64 18446744069414584320, i8* %fmt, i8 1) + store i32 %nm1sl32, i32* getelementptr ([0 x i32], [0 x i32]* @asiz, i32 0, i32 2) + + ret void +} diff --git a/llvm/test/Transforms/InstCombine/snprintf.ll b/llvm/test/Transforms/InstCombine/snprintf.ll --- a/llvm/test/Transforms/InstCombine/snprintf.ll +++ b/llvm/test/Transforms/InstCombine/snprintf.ll @@ -92,10 +92,10 @@ ret i32 %call } -define i32 @test_char_wrong_size(i8* %buf) #0 { -; CHECK-LABEL: @test_char_wrong_size( -; CHECK-NEXT: [[CALL:%.*]] = call i32 (i8*, i64, i8*, ...) @snprintf(i8* noundef nonnull dereferenceable(1) [[BUF:%.*]], i64 1, i8* getelementptr inbounds ([3 x i8], [3 x i8]* @.str.2, i64 0, i64 0), i32 65) -; CHECK-NEXT: ret i32 [[CALL]] +define i32 @test_char_small_size(i8* %buf) #0 { +; CHECK-LABEL: @test_char_small_size( +; CHECK-NEXT: store i8 0, i8* [[BUF:%.*]], align 1 +; CHECK-NEXT: ret i32 1 ; %call = call i32 (i8*, i64, i8*, ...) @snprintf(i8* %buf, i64 1, i8* getelementptr inbounds ([3 x i8], [3 x i8]* @.str.2, i64 0, i64 0), i32 65) #2 ret i32 %call @@ -120,10 +120,10 @@ ret i32 %call } -define i32 @test_str_wrong_size(i8* %buf) #0 { -; CHECK-LABEL: @test_str_wrong_size( -; CHECK-NEXT: [[CALL:%.*]] = call i32 (i8*, i64, i8*, ...) @snprintf(i8* noundef nonnull dereferenceable(1) [[BUF:%.*]], i64 1, i8* getelementptr inbounds ([3 x i8], [3 x i8]* @.str.3, i64 0, i64 0), i8* getelementptr inbounds ([4 x i8], [4 x i8]* @.str, i64 0, i64 0)) -; CHECK-NEXT: ret i32 [[CALL]] +define i32 @test_str_small_size(i8* %buf) #0 { +; CHECK-LABEL: @test_str_small_size( +; CHECK-NEXT: store i8 0, i8* [[BUF:%.*]], align 1 +; CHECK-NEXT: ret i32 3 ; %call = call i32 (i8*, i64, i8*, ...) @snprintf(i8* %buf, i64 1, i8* getelementptr inbounds ([3 x i8], [3 x i8]* @.str.3, i64 0, i64 0), i8* getelementptr inbounds ([4 x i8], [4 x i8]* @.str, i64 0, i64 0)) #2 ret i32 %call