Skip to content

Commit

Permalink
[InstCombine] Remove assumptions about int having 32 bits
Browse files Browse the repository at this point in the history
Reviewed By: bjope

Differential Revision: https://reviews.llvm.org/D131731
  • Loading branch information
msebor committed Aug 16, 2022
1 parent 1fe7200 commit e858f51
Show file tree
Hide file tree
Showing 8 changed files with 348 additions and 23 deletions.
13 changes: 5 additions & 8 deletions llvm/lib/Transforms/Utils/BuildLibCalls.cpp
Expand Up @@ -1756,22 +1756,19 @@ Value *llvm::emitBinaryFloatFnCall(Value *Op1, Value *Op2,
return emitBinaryFloatFnCallHelper(Op1, Op2, TheLibFunc, Name, B, Attrs, TLI);
}

// Emit a call to putchar(int) with Char as the argument. Char must have
// the same precision as int, which need not be 32 bits.
Value *llvm::emitPutChar(Value *Char, IRBuilderBase &B,
const TargetLibraryInfo *TLI) {
Module *M = B.GetInsertBlock()->getModule();
if (!isLibFuncEmittable(M, TLI, LibFunc_putchar))
return nullptr;

Type *Ty = Char->getType();
StringRef PutCharName = TLI->getName(LibFunc_putchar);
FunctionCallee PutChar = getOrInsertLibFunc(M, *TLI, LibFunc_putchar,
B.getInt32Ty(), B.getInt32Ty());
FunctionCallee PutChar = getOrInsertLibFunc(M, *TLI, LibFunc_putchar, Ty, Ty);
inferNonMandatoryLibFuncAttrs(M, PutCharName, *TLI);
CallInst *CI = B.CreateCall(PutChar,
B.CreateIntCast(Char,
B.getInt32Ty(),
/*isSigned*/true,
"chari"),
PutCharName);
CallInst *CI = B.CreateCall(PutChar, Char, PutCharName);

if (const Function *F =
dyn_cast<Function>(PutChar.getCallee()->stripPointerCasts()))
Expand Down
54 changes: 39 additions & 15 deletions llvm/lib/Transforms/Utils/SimplifyLibCalls.cpp
Expand Up @@ -2550,21 +2550,24 @@ void LibCallSimplifier::classifyArgUse(
//===----------------------------------------------------------------------===//

Value *LibCallSimplifier::optimizeFFS(CallInst *CI, IRBuilderBase &B) {
// ffs(x) -> x != 0 ? (i32)llvm.cttz(x)+1 : 0
// All variants of ffs return int which need not be 32 bits wide.
// ffs{,l,ll}(x) -> x != 0 ? (int)llvm.cttz(x)+1 : 0
Type *RetType = CI->getType();
Value *Op = CI->getArgOperand(0);
Type *ArgType = Op->getType();
Function *F = Intrinsic::getDeclaration(CI->getCalledFunction()->getParent(),
Intrinsic::cttz, ArgType);
Value *V = B.CreateCall(F, {Op, B.getTrue()}, "cttz");
V = B.CreateAdd(V, ConstantInt::get(V->getType(), 1));
V = B.CreateIntCast(V, B.getInt32Ty(), false);
V = B.CreateIntCast(V, RetType, false);

Value *Cond = B.CreateICmpNE(Op, Constant::getNullValue(ArgType));
return B.CreateSelect(Cond, V, B.getInt32(0));
return B.CreateSelect(Cond, V, ConstantInt::get(RetType, 0));
}

Value *LibCallSimplifier::optimizeFls(CallInst *CI, IRBuilderBase &B) {
// fls(x) -> (i32)(sizeInBits(x) - llvm.ctlz(x, false))
// All variants of fls return int which need not be 32 bits wide.
// fls{,l,ll}(x) -> (int)(sizeInBits(x) - llvm.ctlz(x, false))
Value *Op = CI->getArgOperand(0);
Type *ArgType = Op->getType();
Function *F = Intrinsic::getDeclaration(CI->getCalledFunction()->getParent(),
Expand All @@ -2587,15 +2590,17 @@ Value *LibCallSimplifier::optimizeAbs(CallInst *CI, IRBuilderBase &B) {
Value *LibCallSimplifier::optimizeIsDigit(CallInst *CI, IRBuilderBase &B) {
// isdigit(c) -> (c-'0') <u 10
Value *Op = CI->getArgOperand(0);
Op = B.CreateSub(Op, B.getInt32('0'), "isdigittmp");
Op = B.CreateICmpULT(Op, B.getInt32(10), "isdigit");
Type *ArgType = Op->getType();
Op = B.CreateSub(Op, ConstantInt::get(ArgType, '0'), "isdigittmp");
Op = B.CreateICmpULT(Op, ConstantInt::get(ArgType, 10), "isdigit");
return B.CreateZExt(Op, CI->getType());
}

Value *LibCallSimplifier::optimizeIsAscii(CallInst *CI, IRBuilderBase &B) {
// isascii(c) -> c <u 128
Value *Op = CI->getArgOperand(0);
Op = B.CreateICmpULT(Op, B.getInt32(128), "isascii");
Type *ArgType = Op->getType();
Op = B.CreateICmpULT(Op, ConstantInt::get(ArgType, 128), "isascii");
return B.CreateZExt(Op, CI->getType());
}

Expand Down Expand Up @@ -2701,9 +2706,15 @@ Value *LibCallSimplifier::optimizePrintFString(CallInst *CI, IRBuilderBase &B) {
if (!CI->use_empty())
return nullptr;

Type *IntTy = CI->getType();
// printf("x") -> putchar('x'), even for "%" and "%%".
if (FormatStr.size() == 1 || FormatStr == "%%")
return copyFlags(*CI, emitPutChar(B.getInt32(FormatStr[0]), B, TLI));
if (FormatStr.size() == 1 || FormatStr == "%%") {
// Convert the character to unsigned char before passing it to putchar
// to avoid host-specific sign extension in the IR. Putchar converts
// it to unsigned char regardless.
Value *IntChar = ConstantInt::get(IntTy, (unsigned char)FormatStr[0]);
return copyFlags(*CI, emitPutChar(IntChar, B, TLI));
}

// Try to remove call or emit putchar/puts.
if (FormatStr == "%s" && CI->arg_size() > 1) {
Expand All @@ -2714,8 +2725,13 @@ Value *LibCallSimplifier::optimizePrintFString(CallInst *CI, IRBuilderBase &B) {
if (OperandStr.empty())
return (Value *)CI;
// printf("%s", "a") --> putchar('a')
if (OperandStr.size() == 1)
return copyFlags(*CI, emitPutChar(B.getInt32(OperandStr[0]), B, TLI));
if (OperandStr.size() == 1) {
// Convert the character to unsigned char before passing it to putchar
// to avoid host-specific sign extension in the IR. Putchar converts
// it to unsigned char regardless.
Value *IntChar = ConstantInt::get(IntTy, (unsigned char)OperandStr[0]);
return copyFlags(*CI, emitPutChar(IntChar, B, TLI));
}
// printf("%s", str"\n") --> puts(str)
if (OperandStr.back() == '\n') {
OperandStr = OperandStr.drop_back();
Expand All @@ -2738,8 +2754,12 @@ Value *LibCallSimplifier::optimizePrintFString(CallInst *CI, IRBuilderBase &B) {
// Optimize specific format strings.
// printf("%c", chr) --> putchar(chr)
if (FormatStr == "%c" && CI->arg_size() > 1 &&
CI->getArgOperand(1)->getType()->isIntegerTy())
return copyFlags(*CI, emitPutChar(CI->getArgOperand(1), B, TLI));
CI->getArgOperand(1)->getType()->isIntegerTy()) {
// Convert the argument to the type expected by putchar, i.e., int, which
// need not be 32 bits wide but which is the same as printf's return type.
Value *IntChar = B.CreateIntCast(CI->getArgOperand(1), IntTy, false);
return copyFlags(*CI, emitPutChar(IntChar, B, TLI));
}

// printf("%s\n", str) --> puts(str)
if (FormatStr == "%s\n" && CI->arg_size() > 1 &&
Expand Down Expand Up @@ -3192,8 +3212,12 @@ Value *LibCallSimplifier::optimizePuts(CallInst *CI, IRBuilderBase &B) {
// Check for a constant string.
// puts("") -> putchar('\n')
StringRef Str;
if (getConstantStringInfo(CI->getArgOperand(0), Str) && Str.empty())
return copyFlags(*CI, emitPutChar(B.getInt32('\n'), B, TLI));
if (getConstantStringInfo(CI->getArgOperand(0), Str) && Str.empty()) {
// putchar takes an argument of the same type as puts returns, i.e.,
// int, which need not be 32 bits wide.
Type *IntTy = CI->getType();
return copyFlags(*CI, emitPutChar(ConstantInt::get(IntTy, '\n'), B, TLI));
}

return nullptr;
}
Expand Down
35 changes: 35 additions & 0 deletions llvm/test/Transforms/InstCombine/ffs-i16.ll
@@ -0,0 +1,35 @@
; NOTE: Assertions have been autogenerated by utils/update_test_checks.py
;
; Test that the ffs library call simplifier works correctly even for
; targets with 16-bit int.
;
; RUN: opt < %s -mtriple=avr-linux -passes=instcombine -S | FileCheck %s
; RUN: opt < %s -mtriple=msp430-linux -passes=instcombine -S | FileCheck %s

declare i16 @ffs(i16)

declare void @sink(i16)


define void @fold_ffs(i16 %x) {
; CHECK-LABEL: @fold_ffs(
; CHECK-NEXT: call void @sink(i16 0)
; CHECK-NEXT: call void @sink(i16 1)
; CHECK-NEXT: [[CTTZ:%.*]] = call i16 @llvm.cttz.i16(i16 [[X:%.*]], i1 true), !range [[RNG0:![0-9]+]]
; CHECK-NEXT: [[TMP1:%.*]] = add nuw nsw i16 [[CTTZ]], 1
; CHECK-NEXT: [[DOTNOT:%.*]] = icmp eq i16 [[X]], 0
; CHECK-NEXT: [[TMP2:%.*]] = select i1 [[DOTNOT]], i16 0, i16 [[TMP1]]
; CHECK-NEXT: call void @sink(i16 [[TMP2]])
; CHECK-NEXT: ret void
;
%n0 = call i16 @ffs(i16 0)
call void @sink(i16 %n0)

%n1 = call i16 @ffs(i16 1)
call void @sink(i16 %n1)

%nx = call i16 @ffs(i16 %x)
call void @sink(i16 %nx)

ret void
}
34 changes: 34 additions & 0 deletions llvm/test/Transforms/InstCombine/fls-i16.ll
@@ -0,0 +1,34 @@
; NOTE: Assertions have been autogenerated by utils/update_test_checks.py
;
; Test that the fls library call simplifier works correctly even for
; targets with 16-bit int. Although fls is available on a number of
; targets it's supported (hardcoded as available) only on FreeBSD.
;
; RUN: opt < %s -mtriple=avr-freebsd -passes=instcombine -S | FileCheck %s
; RUN: opt < %s -mtriple=msp430-freebsd -passes=instcombine -S | FileCheck %s

declare i16 @fls(i16)

declare void @sink(i16)


define void @fold_fls(i16 %x) {
; CHECK-LABEL: @fold_fls(
; CHECK-NEXT: call void @sink(i16 0)
; CHECK-NEXT: call void @sink(i16 1)
; CHECK-NEXT: [[CTLZ:%.*]] = call i16 @llvm.ctlz.i16(i16 [[X:%.*]], i1 false), !range [[RNG0:![0-9]+]]
; CHECK-NEXT: [[TMP1:%.*]] = sub nuw nsw i16 16, [[CTLZ]]
; CHECK-NEXT: call void @sink(i16 [[TMP1]])
; CHECK-NEXT: ret void
;
%n0 = call i16 @fls(i16 0)
call void @sink(i16 %n0)

%n1 = call i16 @fls(i16 1)
call void @sink(i16 %n1)

%nx = call i16 @fls(i16 %x)
call void @sink(i16 %nx)

ret void
}
57 changes: 57 additions & 0 deletions llvm/test/Transforms/InstCombine/isascii-i16.ll
@@ -0,0 +1,57 @@
; NOTE: Assertions have been autogenerated by utils/update_test_checks.py
; Test that the isascii library call simplifier works correctly even for
; targets with 16-bit int.
;
; RUN: opt < %s -mtriple=avr-freebsd -passes=instcombine -S | FileCheck %s
; RUN: opt < %s -mtriple=msp430-linux -passes=instcombine -S | FileCheck %s

declare i16 @isascii(i16)

declare void @sink(i16)


define void @fold_isascii(i16 %c) {
; CHECK-LABEL: @fold_isascii(
; CHECK-NEXT: call void @sink(i16 1)
; CHECK-NEXT: call void @sink(i16 1)
; CHECK-NEXT: call void @sink(i16 1)
; CHECK-NEXT: call void @sink(i16 0)
; CHECK-NEXT: call void @sink(i16 0)
; CHECK-NEXT: call void @sink(i16 0)
; CHECK-NEXT: call void @sink(i16 0)
; CHECK-NEXT: call void @sink(i16 0)
; CHECK-NEXT: [[ISASCII:%.*]] = icmp ult i16 [[C:%.*]], 128
; CHECK-NEXT: [[TMP1:%.*]] = zext i1 [[ISASCII]] to i16
; CHECK-NEXT: call void @sink(i16 [[TMP1]])
; CHECK-NEXT: ret void
;
%i0 = call i16 @isascii(i16 0)
call void @sink(i16 %i0)

%i1 = call i16 @isascii(i16 1)
call void @sink(i16 %i1)

%i127 = call i16 @isascii(i16 127)
call void @sink(i16 %i127)

%i128 = call i16 @isascii(i16 128)
call void @sink(i16 %i128)

%i255 = call i16 @isascii(i16 255)
call void @sink(i16 %i255)

%i256 = call i16 @isascii(i16 256)
call void @sink(i16 %i256)

; Fold isascii(INT_MAX) to 0. The call is valid with all int values.
%imax = call i16 @isascii(i16 32767)
call void @sink(i16 %imax)

%uimax = call i16 @isascii(i16 65535)
call void @sink(i16 %uimax)

%ic = call i16 @isascii(i16 %c)
call void @sink(i16 %ic)

ret void
}
82 changes: 82 additions & 0 deletions llvm/test/Transforms/InstCombine/isdigit-i16.ll
@@ -0,0 +1,82 @@
; NOTE: Assertions have been autogenerated by utils/update_test_checks.py
; Test that the isdigit library call simplifier works correctly even for
; targets with 16-bit int.
;
; RUN: opt < %s -mtriple=avr-linux -passes=instcombine -S | FileCheck %s
; RUN: opt < %s -mtriple=msp430-freebsd -passes=instcombine -S | FileCheck %s

declare i16 @isdigit(i16)

declare void @sink(i16)

define void @fold_isdigit(i16 %c) {
; CHECK-LABEL: @fold_isdigit(
; CHECK-NEXT: call void @sink(i16 0)
; CHECK-NEXT: call void @sink(i16 0)
; CHECK-NEXT: call void @sink(i16 0)
; CHECK-NEXT: call void @sink(i16 1)
; CHECK-NEXT: call void @sink(i16 1)
; CHECK-NEXT: call void @sink(i16 1)
; CHECK-NEXT: call void @sink(i16 0)
; CHECK-NEXT: call void @sink(i16 0)
; CHECK-NEXT: call void @sink(i16 0)
; CHECK-NEXT: call void @sink(i16 0)
; CHECK-NEXT: call void @sink(i16 0)
; CHECK-NEXT: call void @sink(i16 0)
; CHECK-NEXT: [[ISDIGITTMP:%.*]] = add i16 [[C:%.*]], -48
; CHECK-NEXT: [[ISDIGIT:%.*]] = icmp ult i16 [[ISDIGITTMP]], 10
; CHECK-NEXT: [[TMP1:%.*]] = zext i1 [[ISDIGIT]] to i16
; CHECK-NEXT: call void @sink(i16 [[TMP1]])
; CHECK-NEXT: ret void
;
%i0 = call i16 @isdigit(i16 0)
call void @sink(i16 %i0)

%i1 = call i16 @isdigit(i16 1)
call void @sink(i16 %i1)

; Fold isdigit('/') to 0.
%i47 = call i16 @isdigit(i16 47)
call void @sink(i16 %i47)

; Fold isdigit('0') to 1.
%i48 = call i16 @isdigit(i16 48)
call void @sink(i16 %i48)

; Fold isdigit('1') to 1.
%i49 = call i16 @isdigit(i16 49)
call void @sink(i16 %i49)

; Fold isdigit('9') to 1.
%i57 = call i16 @isdigit(i16 57)
call void @sink(i16 %i57)

; Fold isdigit(':') to 0.
%i58 = call i16 @isdigit(i16 58)
call void @sink(i16 %i58)

%i127 = call i16 @isdigit(i16 127)
call void @sink(i16 %i127)

%i128 = call i16 @isdigit(i16 128)
call void @sink(i16 %i128)

%i255 = call i16 @isdigit(i16 255)
call void @sink(i16 %i255)

; Fold isdigit(256) to 0. The argument is required to be representable
; in unsigned char but it's a common mistake to call the function with
; other arguments and it's arguably safer to fold such calls than to
; let the library call return an arbitrary value or crash.
%i256 = call i16 @isdigit(i16 256)
call void @sink(i16 %i256)

; Same as above.
%imax = call i16 @isdigit(i16 32767)
call void @sink(i16 %imax)

%ic = call i16 @isdigit(i16 %c)
call void @sink(i16 %ic)

ret void
}

0 comments on commit e858f51

Please sign in to comment.