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 @@ -2830,33 +2830,52 @@ if (!Size) return nullptr; + Value *DstArg = CI->getArgOperand(0); + Value *FmtArg = CI->getArgOperand(2); + uint64_t N = Size->getZExtValue(); // 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 (FormatStr.contains('%') || N > (uint64_t)maxIntN(32)) + // Bail if the format string contains a formatting directive or + // if the bound exceeds INT_MAX. POSIX requires implementations + // to set errno to EOVERFLOW for the latter. + return nullptr; if (N == 0) return ConstantInt::get(CI->getType(), FormatStr.size()); - else if (N < FormatStr.size() + 1) - return nullptr; - // snprintf(dst, size, fmt) -> llvm.memcpy(align 1 dst, align 1 fmt, - // strlen(fmt)+1) + if (N >= FormatStr.size()) + // Copy the full format string, including the terminating nul (which + // must be present regardless of the bound). + N = FormatStr.size() + 1; + + // Transform snprintf(dst, N, fmt) to lvm.memcpy(dst, fmt, N') with + // N' <= strlen(dst) + 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()); + DstArg, Align(1), FmtArg, Align(1), + ConstantInt::get(DL.getIntPtrType(CI->getContext()), N))); + + if (N == FormatStr.size() + 1) + // Return early when the whole format string, including the final nul, + // has been copied. + return ConstantInt::get(CI->getType(), N - 1); + + // When truncating the format string append a terminating nul. + Type *Int8Ty = B.getInt8Ty(); + Value *NBytes = B.getInt32(N); + Value *DstEnd = B.CreateInBoundsGEP(Int8Ty, DstArg, NBytes, "endptr"); + B.CreateStore(ConstantInt::get(Int8Ty, 0), DstEnd); + return NBytes; } // The remaining optimizations require the format string to be "%s" or "%c" @@ -2874,7 +2893,7 @@ 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); + 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); @@ -2894,7 +2913,7 @@ return nullptr; copyFlags( - *CI, B.CreateMemCpy(CI->getArgOperand(0), Align(1), + *CI, B.CreateMemCpy(DstArg, Align(1), CI->getArgOperand(3), Align(1), ConstantInt::get(CI->getType(), Str.size() + 1))); 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,133 @@ +; NOTE: Assertions have been autogenerated by utils/update_test_checks.py +; Verify that snprintf calls with a constant size and constant string +; with no formatting directives are transformed into memcpy. +; +; 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_cst() { +; BE-LABEL: @fold_snprintf_cst( +; 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: [[PD34:%.*]] = load i32*, i32** bitcast (i8** getelementptr inbounds ([0 x i8*], [0 x i8*]* @adst, i64 0, i64 3) to i32**), align 8 +; BE-NEXT: store i32 825373440, i32* [[PD34]], 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: [[TMP1:%.*]] = bitcast i8* [[PD2]] to i16* +; BE-NEXT: store i16 12594, i16* [[TMP1]], align 1 +; BE-NEXT: [[ENDPTR:%.*]] = getelementptr inbounds i8, i8* [[PD2]], i64 2 +; BE-NEXT: store i8 0, i8* [[ENDPTR]], align 1 +; BE-NEXT: store i32 2, 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 49, i8* [[PD1]], align 1 +; BE-NEXT: [[ENDPTR5:%.*]] = getelementptr inbounds i8, i8* [[PD1]], i64 1 +; BE-NEXT: store i8 0, i8* [[ENDPTR5]], align 1 +; BE-NEXT: store i32 1, 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_cst( +; 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: [[PD34:%.*]] = load i32*, i32** bitcast (i8** getelementptr inbounds ([0 x i8*], [0 x i8*]* @adst, i64 0, i64 3) to i32**), align 8 +; LE-NEXT: store i32 3355185, i32* [[PD34]], 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: [[TMP1:%.*]] = bitcast i8* [[PD2]] to i16* +; LE-NEXT: store i16 12849, i16* [[TMP1]], align 1 +; LE-NEXT: [[ENDPTR:%.*]] = getelementptr inbounds i8, i8* [[PD2]], i64 2 +; LE-NEXT: store i8 0, i8* [[ENDPTR]], align 1 +; LE-NEXT: store i32 2, 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 49, i8* [[PD1]], align 1 +; LE-NEXT: [[ENDPTR5:%.*]] = getelementptr inbounds i8, i8* [[PD1]], i64 1 +; LE-NEXT: store i8 0, i8* [[ENDPTR5]], align 1 +; LE-NEXT: store i32 1, 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_ximax_cst() { +; ANY-LABEL: @call_snprintf_ximax_cst( +; 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 +}