Casting from float to unsigned char - incorrect output?

I am compiling the following code for the MIPS architecture:

unsigned char trunc(float f) {
return (unsigned char) f;
}

and it produces the following assembly (directives removed for convenience:
trunc:
trunc.w.s $f0, $f12
mfc1 $2, $f0
jr $ra
nop

However, this does not seem to produce the correct output for negative numbers. When I run the following code, I get -1 instead of 255 (which is produced by compiling natively with gcc).
int trunc(float c);
int main() {
printf("%d\n", trunc(-1.0));
}

I am running the mips code on a PISA simulator (SimpleScalar’s Simple-Sim 3.0) instead of a MIPS IV simulator, so there is a little bit of translation occurring before I can simulate it; here is the revised code:
trunc:
cvt.w.s $f0, $f12
mfc1 $2, $f0
jr $ra
nop
the cvt.w.s function in PISA MIPS is rounding towards zero, so it should meet the specification for trunc.w.s in MIPS IV.

Here are the commands I used to compile test.c (with the trunc function in it):
clang -emit-llvm -mfloat-abi=hard -ccc-host-triple mipsel-unknown-linux -ccc-clang-archs mipsel -O3 -S -o test_unopt.ll test.c
opt -std-compile-opts test_unopt.ll -o test.ll
llc -march=mipsel -mcpu=mips32 -float-abi=hard -relocation-model=static test.ll -o test.s

Here is the llvm intermediate representation:
; ModuleID = ‘test_unopt.ll’
target datalayout = “e-p:32:32:32-i1:8:8-i8:8:32-i16:16:32-i32:32:32-i64:64:64-f32:32:32-f64:64:64-v64:64:64-n32”
target triple = “mipsel-unknown-linux”

define zeroext i8 @trunc(float %f) nounwind readnone {
entry:
%conv = fptoui float %f to i8
ret i8 %conv
}

This is the assembly produced by another compiler; it is more complex, but it produces the expected output, matching with GCC: (directives removed for convenience)
trunc:
mov.s $f4,$f12
la $2,L6
l.s $f6,($2)
c.lt.s $f12,$f6
bc1t .L3
sub.s $f0,$f12,$f6
cvt.w.s $f0,$f0
mfc1 $2,$f0
lui $3,32768
addu $3,$2,$3
j .L4
.L3:
cvt.w.s $f4,$f4
mfc1 $3,$f4
.L4:
sll $2,$3,24
srl $2,$2,24
sll $2,$2,24
srl $2,$2,24
j $31
.data
.align 4
L6:
.word 1325400064

Am I correct in my analysis that LLVM’s assembly output is wrong? Is there a way for me to get the correct output?

That code has undefined behavior; see 6.3.1.4p1 in C99

-Eli

I am compiling the following code for the MIPS architecture:

unsigned char trunc(float f) {
        return (unsigned char) f;
}

and it produces the following assembly (directives removed for convenience:
trunc:
    trunc.w.s $f0, $f12
    mfc1 $2, $f0
    jr $ra
    nop

However, this does not seem to produce the correct output for negative numbers. When I run the following code, I get -1 instead of 255 (which is produced by compiling natively with gcc).
int trunc(float c);
int main() {
  printf("%d\n", trunc(-1.0));
}

The prototype for trunc() here and it's definition above don't match. Dunno if that's related to the behavior you're seeing, but it's definitely a bug in the source code.

I didn’t realize the code was undefined, I’ll let my professor know; but for code comparison purposes we’re still seeking advice on producing the output that matches the other compilers (even if it involves doing a translation on the .ll file or additional translation to the produced assembly). We can’t fairly compare the code if it doesn’t do the same thing. This bug came up in susan from the mibench test suite at the end of setup_brightness_lut where it casts float temp to an unsigned char. I don’t think we can include the results in our report if we change the tests.

This should work if your float is between INT_MIN and INT_MAX:

unsigned char trunc(float f) {
return (unsigned char)(int)f;
}

int main() {
printf("%d\n", trunc(-1.0));
}

For this, Clang produces:

trunc:

trunc.w.s $f0, $f12

mfc1 $2, $f0
andi $2, $2, 255
jr $ra
nop