Little explanation of this behaviour

I tried to emit IR for a function that returns the sum between an signed char and a double in C++, just to see how Clang handles type implicit casting.
Can you explain me why Clang converted the char type into a 32-bit integer type before casting it to a floating point? Can a sitofp i8 %3 to double be done or is it wrong?

define i32 @_Z5sumad(i8 signext %x, double %y) #0 {
%1 = alloca i8, align 1
%2 = alloca double, align 8
store i8 %x, i8* %1, align 1
store double %y, double* %2, align 8
%3 = load i8, i8* %1, align 1
%4 = sext i8 %3 to i32
%5 = sitofp i32 %4 to double
%6 = load double, double* %2, align 8
%7 = fadd double %5, %6
%8 = fptosi double %7 to i32
ret i32 %8
}

Hi Lorenzo,

Can you explain me why Clang converted the char type into a 32-bit integer
type before casting it to a floating point?

C and C++ have what's called "integer promotion rules", which apply to
most expressions involving types smaller than int and insert an
implicit promotion to int before anything else happens (in this case
another implicit conversion to double). Clang is emitting the natural
IR for this AST:

  `-CompoundStmt 0x10307d140 <col:29, line:3:1>
    `-ReturnStmt 0x10307d128 <line:2:3, col:14>
      `-ImplicitCastExpr 0x10307d110 <col:10, col:14> 'int' <FloatingToIntegral>
        `-BinaryOperator 0x10307d0e8 <col:10, col:14> 'double' '+'
          >-ImplicitCastExpr 0x10307d0d0 <col:10> 'double' <IntegralToFloating>
          > `-ImplicitCastExpr 0x10307d0a0 <col:10> 'int' <IntegralCast>
          > `-ImplicitCastExpr 0x10307d088 <col:10> 'char' <LValueToRValue>
          > `-DeclRefExpr 0x10307d038 <col:10> 'char' lvalue
ParmVar 0x10307ce00 'x' 'char'
          `-ImplicitCastExpr 0x10307d0b8 <col:14> 'double' <LValueToRValue>
            `-DeclRefExpr 0x10307d060 <col:14> 'double' lvalue ParmVar
0x10307ce70 'y' 'double'

(found by running "clang++ -Xclang -ast-dump tmp.cpp"). Notice there's
an IntegralCast followed by an IntegralToFloating.

Can a sitofp i8 %3 to double be done or is it wrong?

That's fine, in fact LLVM optimizes the function to use that itself.

Cheers.

Tim.

Thanks, so what’s the point of these rules? Do they grant something like safety or faster execution?

C and C++ have what's called "integer promotion rules", which apply to
most expressions involving types smaller than int and insert an
implicit promotion to int before anything else happens (in this case
another implicit conversion to double).

Can a sitofp i8 %3 to double be done or is it wrong?

That's fine, in fact LLVM optimizes the function to use that itself.

Are you saying that instruction will be optimized by LLVM in this case?

Thanks, so what’s the point of these rules? Do they grant something like safety or faster execution?

My guess is just backwards compatibility from the earliest C compilers
(where it might even have been simply to make implementation easier).
There's certainly no real safety benefits.

I suppose it does tend to match how the instructions actually get
implemented (most backends will use an 32-bit addition even for an
incoming "add i8 %l, %r") so exposing that early might give more
optimization opportunities, but that's a bit tenuous.

Are you saying that instruction will be optimized by LLVM in this case?

Yes. LLVM will convert the "%int32 = sext i8 %val to i32; %res =
sitofp i32 %int32 to double" sequence into "%res = sitofp i8 %val to
double".

Cheers.

Tim.

Many or most CPUs don't even have narrower size adds or subtracts --
smaller values get zero or sign extended when loaded from memory, worked on
in full register size, and truncated when written back to memory.

The PDP-11 was like that. There was a bit in the instruction encoding to
specify word or byte operand, and this worked with mov and cmp, but what
you'd think would be the encoding for add.b in fact turns out to be sub!

x86 and 68k can do arithmetic on different size operands directly, but
RISCs in general can't. Aarch64 is quite unusual in having a instruction
bit to specify 64 or 32 bit operations -- I'm expecting this means ARM will
eventually introduce low end implementations with a 32 bit ALU and 64 bit
operations will take longer. That's *not* the case with at A53 and A57, but
I haven't had a chance to look at the A35 specs yet.