[InstCombine] Adjust snprintf folding of constant strings (PR #56598)

Reviewed By: efriedma

Differential Revision: https://reviews.llvm.org/D130494
This commit is contained in:
Martin Sebor 2022-07-27 15:59:50 -06:00
parent 6f4f9e316c
commit 65967708d2
6 changed files with 511 additions and 63 deletions

View File

@ -222,6 +222,8 @@ private:
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,

View File

@ -2908,6 +2908,60 @@ Value *LibCallSimplifier::optimizeSPrintF(CallInst *CI, IRBuilderBase &B) {
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 @@ Value *LibCallSimplifier::optimizeSnPrintFString(CallInst *CI,
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) {
if (FormatStr.size() != 2 || FormatStr[0] != '%' || CI->arg_size() != 4)
return nullptr;
// 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);
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;
if (N == 0)
return ConstantInt::get(CI->getType(), Str.size());
else if (N < Str.size() + 1)
return nullptr;
copyFlags(
*CI, B.CreateMemCpy(CI->getArgOperand(0), Align(1),
CI->getArgOperand(3), Align(1),
ConstantInt::get(CI->getType(), Str.size() + 1)));
// The snprintf result is the unincremented number of bytes in the string.
return ConstantInt::get(CI->getType(), Str.size());
}
// 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);
}
return nullptr;
if (FormatStr[1] != 's')
return nullptr;
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;
return emitSnPrintfMemCpy(CI, StrArg, Str, N, B);
}
Value *LibCallSimplifier::optimizeSnPrintF(CallInst *CI, IRBuilderBase &B) {

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -92,10 +92,10 @@ define i32 @test_char_zero_size(i8* %buf) #0 {
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 @@ define i32 @test_str_zero_size(i8* %buf) #0 {
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