Bug Report (Float --> Integer Inplace Conversion Precision Problem)

David Welling dwelling@dsrnet.com
Mon May 7 06:57:00 GMT 2001


Originator
Joe Murray, John Casserly, Dave Welling

Organization
	DSR (Digital Systems Research)

Confidential
	No

Synopsis
Float --> Integer Inplace Conversion Precision Problem

Severity
Critical

Priority
high

Category
c

Class
The machine code generated by GCC is incorrect.
ice-on-legal-code

Release
 	Error occurs in:
	GCC-2.9.5 and egcs 1.1.2

Environment
Various Machines using the INTEL processor.

Description
A problem was discovered when performing an inplace cast from an integer to
a float.

Consider the following program compiled with no options.
--------------------------------------------
#include <stdio.h>

int main(void) {

int iaa =28;
int ibb =80;
int icc =20;

int imm;
int imm0;
float fll;
float ftmp;

fll = (float)iaa / (float)ibb;

imm = ftmp = fll *(float)icc;

printf("Direct assign float to float = %.10f\n", ftmp);
printf("Inplace assign to int = %d\n", imm);

imm0 = ftmp;

printf("Direct assign float to int = %d\n", imm0);

return (0);

}

------------------------
Using GCC (egcs1.1.2) compiler, the result is:

Direct assign float to float = 7
Inplace assign to int = 6
Direct assign float to int = 7

The inplace assignment should result in 7.  It does not because the value is
copied straight from the FPU 80 bit register for the integer imm.  This
truncates the value to 6.  In the case of imm0 the value has been corrected
to 32 bit precision because the value of ftmp was already moved from the FPU
register to memory.

The following lists the aforementioned code in assembly.

----------------------------------------------------------
	.file	"simple.c"
	.version	"01.01"
# GNU C version egcs-2.91.66 19990314/Linux (egcs-1.1.2 release)
(i386-redhat-linux) compiled by GNU C version egcs-2.91.66 19990314/Linux
(egcs-1.1.2 release).
# options passed:  -march=i686 -mcpu=i686 -fverbose-asm
# options enabled:  -fpeephole -ffunction-cse -fkeep-static-consts
# -fpcc-struct-return -fcommon -fverbose-asm -fgnu-linker -fargument-alias
# -m80387 -mhard-float -mno-soft-float -mieee-fp -mfp-ret-in-387
# -mschedule-prologue -mcpu=i686 -march=i686

gcc2_compiled.:
.section	.rodata
	.align 32
.LC0:
	.string	"Direct assign float to float = %.10f\n"
	.align 32
.LC1:
	.string	" Inplace assign float to int = %d\n"
	.align 32
.LC2:
	.string	"  Direct assign float to int = %d\n"
.text
	.align 4
.globl main
	.type	 main,@function
main:
	pushl %ebp
	movl %esp,%ebp
	subl $40,%esp
	movl $28,-4(%ebp)
	movl $80,-8(%ebp)
	movl $20,-12(%ebp)
	fildl -4(%ebp)
	fildl -8(%ebp)
	fdivrp %st,%st(1)
	fstps -24(%ebp)
	fildl -12(%ebp)
	fmuls -24(%ebp)
	fsts -28(%ebp)
	fnstcw -32(%ebp)
	movl -32(%ebp),%edx
	movb $12,%dh
	movl %edx,-40(%ebp)
	fldcw -40(%ebp)
	fistpl -16(%ebp)
	fldcw -32(%ebp)
	flds -28(%ebp)
	subl $8,%esp
	fstpl (%esp)
	pushl $.LC0
	call printf
	addl $12,%esp
	movl -16(%ebp),%eax
	pushl %eax
	pushl $.LC1
	call printf
	addl $8,%esp
	flds -28(%ebp)
	fnstcw -32(%ebp)
	movl -32(%ebp),%edx
	movb $12,%dh
	movl %edx,-40(%ebp)
	fldcw -40(%ebp)
	fistpl -20(%ebp)
	fldcw -32(%ebp)
	movl -20(%ebp),%eax
	pushl %eax
	pushl $.LC2
	call printf
	addl $8,%esp
	xorl %eax,%eax
	jmp .L1
	.p2align 4,,7
.L1:
	movl %ebp,%esp
	popl %ebp
	ret
.Lfe1:
	.size	 main,.Lfe1-main
	.ident	"GCC: (GNU) egcs-2.91.66 19990314/Linux (egcs-1.1.2 release)"

-------------------------------------------------------------

The following lists the inplace cast from the above assembly code

	fnstcw -32(%ebp)         # Get the FPU Control Word
	movl -32(%ebp),%edx
	movb $12,%dh             # Set the Upper byte to 12 or 0x0c
                                               # (32 bit Resolution Truncate
Mode)
	movl %edx,-40(%ebp)
	fldcw -40(%ebp)          # Store the New FPU Control Word
	fistpl -16(%ebp)           # Perform the Cast
	fldcw -32(%ebp)          # Restore the Original Control Word

The problem is that the fistpl instruction will not correct the 80 bit value
to the meaningful 32 bit resolution of the integer.  This causes the 80 bit
value to be truncated.


How-To-Repeat
Run the above program.

Fix
One solution is to copy the floating point value onto the variable stack and
back to the Floating Point Register before the FPU Register is modified and
before the fistpl instruction is given.  This causes the float to be rounded
to the nearest 32/64 bit value (depending on the size of the float i.e.
float or double).

For Example the above segment would change to the following for a 32 bit
float casted to and integer.

	subl $4, %esp	           # Make Room on the Varible Stack
 	fstps (%esp)	           # Copy the value onto the variable stack
 	flds (%esp) 	           # Copy the value back to the FPU Register
 	addl $4, %esp              # Set the Variable Stack to its original
Position
	fnstcw -32(%ebp)         # Get the FPU Control Word
	movl -32(%ebp),%edx
	movb $12,%dh             # Set the Upper byte to 12 or 0x0c
                                               # (32 bit Resolution Truncate
Mode)
	movl %edx,-40(%ebp)
	fldcw -40(%ebp)          # Store the New FPU Control Word
	fistpl -16(%ebp)           # Perform the Cast
	fldcw -32(%ebp)          # Restore the Original Control Word


This can be accomplished by added the following code to the gcc source file
i386.c and the routine "output_fix_trunc" for gcc(egcs1.1.2).


 if (GET_MODE(operands[1]) == SFmode)
    {
      xops[0] = stack_pointer_rtx;
      xops[1] = GEN_INT(4);
      output_asm_insn ("subl %1,%0", xops);
      output_asm_insn ("fstps (%0)", xops);
      output_asm_insn ("flds (%0)",  xops);
      output_asm_insn ("addl %1,%0", xops);
    }
  else if (GET_MODE(operands[1]) == DFmode)
    {
      xops[0] = stack_pointer_rtx;
      xops[1] = GEN_INT(8);
      output_asm_insn ("subl %1, %0", xops);
      output_asm_insn ("fstpl (%0)",  xops);
      output_asm_insn ("fldl (%0)",   xops);
      output_asm_insn ("addl %1, %0", xops);
    }

or adding the following code to the gcc source file i386.c and the routine
"output_fix_trunc" for gcc-2.9.5

  if (GET_MODE (operands[1]) == SFmode)
    {
      xops[0] = stack_pointer_rtx;
      xops[1] = GEN_INT(4);
      output_asm_insn (AS2(subl,%1,%0), xops);
      output_asm_insn (AS1(fstps,(%0)), xops);
      output_asm_insn (AS1(flds,(%0)), xops);
      output_asm_insn (AS2(addl,%1,%0), xops);
    }
  else if (GET_MODE (operands[1]) == DFmode)


      xops[0] = stack_pointer_rtx;
      xops[1] = GEN_INT(8);
      output_asm_insn (AS2(subl,%1,%0), xops);
      output_asm_insn (AS1(fstpl,(%0)), xops);
      output_asm_insn (AS1(fldl,(%0)), xops);
      output_asm_insn (AS2(addl,%1,%0), xops);
    }





More information about the Gcc-bugs mailing list