diff --git a/llvm/lib/Transforms/Utils/BuildLibCalls.cpp b/llvm/lib/Transforms/Utils/BuildLibCalls.cpp --- a/llvm/lib/Transforms/Utils/BuildLibCalls.cpp +++ b/llvm/lib/Transforms/Utils/BuildLibCalls.cpp @@ -1756,22 +1756,19 @@ 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(PutChar.getCallee()->stripPointerCasts())) 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 @@ -2550,21 +2550,24 @@ //===----------------------------------------------------------------------===// 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(), @@ -2587,15 +2590,17 @@ Value *LibCallSimplifier::optimizeIsDigit(CallInst *CI, IRBuilderBase &B) { // isdigit(c) -> (c-'0') 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 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()); } @@ -2701,9 +2706,15 @@ 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) { @@ -2714,8 +2725,13 @@ 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(); @@ -2738,8 +2754,12 @@ // 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 && @@ -3192,8 +3212,12 @@ // 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; } diff --git a/llvm/test/Transforms/InstCombine/ffs-i16.ll b/llvm/test/Transforms/InstCombine/ffs-i16.ll new file mode 100644 --- /dev/null +++ b/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 +} diff --git a/llvm/test/Transforms/InstCombine/fls-i16.ll b/llvm/test/Transforms/InstCombine/fls-i16.ll new file mode 100644 --- /dev/null +++ b/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 +} diff --git a/llvm/test/Transforms/InstCombine/isascii-i16.ll b/llvm/test/Transforms/InstCombine/isascii-i16.ll new file mode 100644 --- /dev/null +++ b/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 +} diff --git a/llvm/test/Transforms/InstCombine/isdigit-i16.ll b/llvm/test/Transforms/InstCombine/isdigit-i16.ll new file mode 100644 --- /dev/null +++ b/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 +} diff --git a/llvm/test/Transforms/InstCombine/printf-i16.ll b/llvm/test/Transforms/InstCombine/printf-i16.ll new file mode 100644 --- /dev/null +++ b/llvm/test/Transforms/InstCombine/printf-i16.ll @@ -0,0 +1,72 @@ +; NOTE: Assertions have been autogenerated by utils/update_test_checks.py +; +; RUN: opt < %s -mtriple=avr-freebsd -passes=instcombine -S | FileCheck %s +; RUN: opt < %s -mtriple=msp430-linux -passes=instcombine -S | FileCheck %s +; +; Verify that the puts to putchar transformation works correctly even for +; targets with 16-bit int. + +declare i16 @putchar(i16) +declare i16 @puts(i8*) + +@s1 = constant [2 x i8] c"\01\00" +@s7f = constant [2 x i8] c"\7f\00" +@s80 = constant [2 x i8] c"\80\00" +@sff = constant [2 x i8] c"\ff\00" + +@pcnt_c = constant [3 x i8] c"%c\00" +@pcnt_s = constant [3 x i8] c"%s\00" + +declare i16 @printf(i8*, ...) + +; Verfify that the three printf to putchar transformations all result +; in the same output for calls with equivalent arguments. + +define void @xform_printf(i8 %c8, i16 %c16) { +; CHECK-LABEL: @xform_printf( +; CHECK-NEXT: [[PUTCHAR:%.*]] = call i16 @putchar(i16 1) +; CHECK-NEXT: [[PUTCHAR1:%.*]] = call i16 @putchar(i16 1) +; CHECK-NEXT: [[PUTCHAR2:%.*]] = call i16 @putchar(i16 1) +; CHECK-NEXT: [[PUTCHAR3:%.*]] = call i16 @putchar(i16 127) +; CHECK-NEXT: [[PUTCHAR4:%.*]] = call i16 @putchar(i16 127) +; CHECK-NEXT: [[PUTCHAR5:%.*]] = call i16 @putchar(i16 127) +; CHECK-NEXT: [[PUTCHAR6:%.*]] = call i16 @putchar(i16 128) +; CHECK-NEXT: [[PUTCHAR7:%.*]] = call i16 @putchar(i16 128) +; CHECK-NEXT: [[PUTCHAR8:%.*]] = call i16 @putchar(i16 128) +; CHECK-NEXT: [[PUTCHAR9:%.*]] = call i16 @putchar(i16 255) +; CHECK-NEXT: [[PUTCHAR10:%.*]] = call i16 @putchar(i16 255) +; CHECK-NEXT: [[PUTCHAR11:%.*]] = call i16 @putchar(i16 255) +; CHECK-NEXT: [[TMP1:%.*]] = zext i8 [[C8:%.*]] to i16 +; CHECK-NEXT: [[PUTCHAR12:%.*]] = call i16 @putchar(i16 [[TMP1]]) +; CHECK-NEXT: [[PUTCHAR13:%.*]] = call i16 @putchar(i16 [[C16:%.*]]) +; CHECK-NEXT: ret void +; + %ppcnt_c = getelementptr [3 x i8], [3 x i8]* @pcnt_c, i32 0, i32 0 + %ppcnt_s = getelementptr [3 x i8], [3 x i8]* @pcnt_s, i32 0, i32 0 + + %ps1 = getelementptr [2 x i8], [2 x i8]* @s1, i32 0, i32 0 + call i16 (i8*, ...) @printf(i8* %ps1) + call i16 (i8*, ...) @printf(i8* %ppcnt_c, i16 1) + call i16 (i8*, ...) @printf(i8* %ppcnt_s, i8* %ps1) + + %ps7f = getelementptr [2 x i8], [2 x i8]* @s7f, i32 0, i32 0 + call i16 (i8*, ...) @printf(i8* %ps7f) + call i16 (i8*, ...) @printf(i8* %ppcnt_c, i16 127) + call i16 (i8*, ...) @printf(i8* %ppcnt_s, i8* %ps7f) + + %ps80 = getelementptr [2 x i8], [2 x i8]* @s80, i32 0, i32 0 + call i16 (i8*, ...) @printf(i8* %ps80) + call i16 (i8*, ...) @printf(i8* %ppcnt_c, i16 128) + call i16 (i8*, ...) @printf(i8* %ppcnt_s, i8* %ps80) + + %psff = getelementptr [2 x i8], [2 x i8]* @sff, i32 0, i32 0 + call i16 (i8*, ...) @printf(i8* %psff) + call i16 (i8*, ...) @printf(i8* %ppcnt_c, i16 255) + call i16 (i8*, ...) @printf(i8* %ppcnt_s, i8* %psff) + +; The i8 argument to printf can be either zero-extended or sign-extended +; when passed to putchar which then converts it to unsigned char. + call i16 (i8*, ...) @printf(i8* %ppcnt_c, i8 %c8) + call i16 (i8*, ...) @printf(i8* %ppcnt_c, i16 %c16) + ret void +} diff --git a/llvm/test/Transforms/InstCombine/puts-i16.ll b/llvm/test/Transforms/InstCombine/puts-i16.ll new file mode 100644 --- /dev/null +++ b/llvm/test/Transforms/InstCombine/puts-i16.ll @@ -0,0 +1,24 @@ +; NOTE: Assertions have been autogenerated by utils/update_test_checks.py +; +; RUN: opt < %s -mtriple=avr-linux -passes=instcombine -S | FileCheck %s +; RUN: opt < %s -mtriple=msp430-freebsd -passes=instcombine -S | FileCheck %s +; +; Test that the puts to putchar transformation works correctly even for +; targets with 16-bit int. + +declare i16 @putchar(i16) +declare i16 @puts(i8*) + +@empty = constant [1 x i8] c"\00" + +define void @xform_puts(i16 %c) { +; CHECK-LABEL: @xform_puts( +; CHECK-NEXT: [[PUTCHAR:%.*]] = call i16 @putchar(i16 10) +; CHECK-NEXT: ret void +; +; Transform puts("") to putchar("\n"). + %s = getelementptr [1 x i8], [1 x i8]* @empty, i32 0, i32 0 + call i16 @puts(i8* %s) + + ret void +}