From: Julian Seward Date: Wed, 8 Sep 2004 23:42:23 +0000 (+0000) Subject: x86: make float to integer conversions observe the rounding mode in X-Git-Tag: svn/VALGRIND_3_0_1^2~1093 X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=a1b5e7008a284fec150dc693ea93f7b43d2a7358;p=thirdparty%2Fvalgrind.git x86: make float to integer conversions observe the rounding mode in effect. git-svn-id: svn://svn.valgrind.org/vex/trunk@241 --- diff --git a/VEX/priv/guest-x86/toIR.c b/VEX/priv/guest-x86/toIR.c index aa0e8584a5..19c285cf1f 100644 --- a/VEX/priv/guest-x86/toIR.c +++ b/VEX/priv/guest-x86/toIR.c @@ -3100,6 +3100,33 @@ static void put_ftop ( IRExpr* e ) } +/* --------- Get/set the FPU control word. --------- */ +/* Note, IA32 has this as a 16-bit value, so fstcw/fldcw need to cast + to/from 16 bits. Here we represent it in 32 bits. */ +static IRExpr* /* :: Ity_I32 */ get_fpucw ( void ) +{ + return IRExpr_Get( OFFB_FPUCW, Ity_I32 ); +} + +static void put_fpucw ( IRExpr* /* :: Ity_I32 */ e ) +{ + stmt( IRStmt_Put( OFFB_FPUCW, e ) ); +} + + +/* --------- Get the FPU rounding mode from the CW. --------- */ +/* Produces a value in 0 .. 3, which is encoded as per the type + IRRoundingMode. On IA32 the relevant value is precisely bits 11 + and 10 of the control word. +*/ +static IRExpr* /* :: Ity_I32 */ get_roundingmode ( void ) +{ + return binop( Iop_And32, + binop(Iop_Shr32, get_fpucw(), mkU8(10)), + mkU32(3) ); +} + + /* --------- Get/set FP register tag bytes. --------- */ /* Given i, generate an expression which is the offset in the guest @@ -3344,7 +3371,7 @@ UInt dis_FPU ( Bool* decode_ok, UChar sorb, UInt delta ) DIP("fldF %s\n", dis_buf); fp_push(); put_ST(0, unop(Iop_F32toF64, - IRExpr_LDle(Ity_F32, mkexpr(addr)))); + loadLE(Ity_F32, mkexpr(addr)))); break; case 3: /* FSTP single-real */ @@ -3353,13 +3380,14 @@ UInt dis_FPU ( Bool* decode_ok, UChar sorb, UInt delta ) fp_pop(); break; - case 5: - vex_printf("vex x86->IR: ignoring fldcw\n"); + case 5: + DIP("fldcw %s", dis_buf); + put_fpucw( unop(Iop_16Uto32, loadLE(Ity_I16, mkexpr(addr))) ); break; case 7: - vex_printf("vex x86->IR: ignoring fstcw\n"); - storeLE(mkexpr(addr), mkU16(0x037F)); + DIP("fstcw %s", dis_buf); + storeLE(mkexpr(addr), unop(Iop_32to16, get_fpucw())); break; default: @@ -3492,9 +3520,16 @@ UInt dis_FPU ( Bool* decode_ok, UChar sorb, UInt delta ) loadLE(Ity_I32, mkexpr(addr)))); break; + case 2: /* FIST m32 */ + DIP("fistl %s", dis_buf); + storeLE( mkexpr(addr), + binop(Iop_F64toI32, get_roundingmode(), get_ST(0)) ); + break; + case 3: /* FISTP m32 */ DIP("fistpl %s", dis_buf); - storeLE(mkexpr(addr), unop(Iop_F64toI32,get_ST(0))); + storeLE( mkexpr(addr), + binop(Iop_F64toI32, get_roundingmode(), get_ST(0)) ); fp_pop(); break; @@ -3610,8 +3645,9 @@ UInt dis_FPU ( Bool* decode_ok, UChar sorb, UInt delta ) case 3: /* FISTP m16 */ DIP("fistps %s", dis_buf); - storeLE(mkexpr(addr), unop(Iop_F64toI16,get_ST(0))); - fp_pop(); + storeLE( mkexpr(addr), + binop(Iop_F64toI16, get_roundingmode(), get_ST(0)) ); + fp_pop(); break; #if 0 @@ -7089,13 +7125,11 @@ static UInt disInstr ( UInt delta, Bool* isEnd ) case 0xB0: /* MOV imm,AL */ case 0xB1: /* MOV imm,CL */ case 0xB2: /* MOV imm,DL */ -#if 0 case 0xB3: /* MOV imm,BL */ case 0xB4: /* MOV imm,AH */ case 0xB5: /* MOV imm,CH */ case 0xB6: /* MOV imm,DH */ case 0xB7: /* MOV imm,BH */ -#endif d32 = getIByte(delta); delta += 1; putIReg(1, opc-0xB0, mkU8(d32)); DIP("movb $0x%x,%s\n", d32, nameIReg(1,opc-0xB0)); diff --git a/VEX/priv/host-x86/hdefs.c b/VEX/priv/host-x86/hdefs.c index e25cad21ee..b638b9e407 100644 --- a/VEX/priv/host-x86/hdefs.c +++ b/VEX/priv/host-x86/hdefs.c @@ -599,6 +599,14 @@ X86Instr* X86Instr_FpCMov ( X86CondCode cond, HReg src, HReg dst ) { vassert(cond != Xcc_ALWAYS); return i; } +X86Instr* X86Instr_FpLdStCW ( Bool isLoad, X86AMode* addr ) +{ + X86Instr* i = LibVEX_Alloc(sizeof(X86Instr)); + i->tag = Xin_FpLdStCW; + i->Xin.FpLdStCW.isLoad = isLoad; + i->Xin.FpLdStCW.addr = addr; + return i; +} void ppX86Instr ( X86Instr* i ) { @@ -759,6 +767,10 @@ void ppX86Instr ( X86Instr* i ) { vex_printf(","); ppHRegX86(i->Xin.FpCMov.dst); return; + case Xin_FpLdStCW: + vex_printf(i->Xin.FpLdStCW.isLoad ? "fldcw " : "fstcw "); + ppX86AMode(i->Xin.FpLdStCW.addr); + return; default: vpanic("ppX86Instr"); } @@ -875,6 +887,9 @@ void getRegUsage_X86Instr (HRegUsage* u, X86Instr* i) addHRegUse(u, HRmRead, i->Xin.FpCMov.src); addHRegUse(u, HRmModify, i->Xin.FpCMov.dst); return; + case Xin_FpLdStCW: + addRegUsage_X86AMode(u, i->Xin.FpLdStCW.addr); + return; default: ppX86Instr(i); vpanic("getRegUsage_X86Instr"); @@ -963,6 +978,9 @@ void mapRegs_X86Instr (HRegRemap* m, X86Instr* i) mapReg(m, &i->Xin.FpCMov.src); mapReg(m, &i->Xin.FpCMov.dst); return; + case Xin_FpLdStCW: + mapRegs_X86AMode(m, i->Xin.FpLdStCW.addr); + return; default: ppX86Instr(i); vpanic("mapRegs_X86Instr"); @@ -1845,6 +1863,15 @@ Int emit_X86Instr ( UChar* buf, Int nbuf, X86Instr* i ) *(ptmp-1) = p - ptmp; goto done; + case Xin_FpLdStCW: + if (i->Xin.FpLdStCW.isLoad) { + *p++ = 0xD9; + p = doAMode_M(p, fake(5)/*subopcode*/, i->Xin.FpLdStCW.addr); + } else { + vassert(0); + } + goto done; + default: goto bad; } diff --git a/VEX/priv/host-x86/hdefs.h b/VEX/priv/host-x86/hdefs.h index ca4de1952f..a657ec2395 100644 --- a/VEX/priv/host-x86/hdefs.h +++ b/VEX/priv/host-x86/hdefs.h @@ -279,7 +279,8 @@ typedef Xin_FpBinary, /* FP fake binary op */ Xin_FpLdSt, /* FP fake load/store */ Xin_FpLdStI, /* FP fake load/store, converting to/from Int */ - Xin_FpCMov /* FP fake floating point (un)conditional move */ + Xin_FpCMov, /* FP fake floating point (un)conditional move */ + Xin_FpLdStCW /* fldcw / fstcw */ } X86InstrTag; @@ -413,6 +414,12 @@ typedef HReg src; HReg dst; } FpCMov; + /* Load/store the FPU's 16-bit control word (fldcw/fstcw) */ + struct { + Bool isLoad; + X86AMode* addr; + } + FpLdStCW; } Xin; } X86Instr; @@ -439,6 +446,8 @@ extern X86Instr* X86Instr_FpBinary ( X86FpOp op, HReg srcL, HReg srcR, HReg dst extern X86Instr* X86Instr_FpLdSt ( Bool isLoad, UChar sz, HReg reg, X86AMode* ); extern X86Instr* X86Instr_FpLdStI ( Bool isLoad, UChar sz, HReg reg, X86AMode* ); extern X86Instr* X86Instr_FpCMov ( X86CondCode, HReg src, HReg dst ); +extern X86Instr* X86Instr_FpLdStCW ( Bool isLoad, X86AMode* ); + extern void ppX86Instr ( X86Instr* ); diff --git a/VEX/priv/host-x86/isel.c b/VEX/priv/host-x86/isel.c index 6d17bec07e..65984edb4d 100644 --- a/VEX/priv/host-x86/isel.c +++ b/VEX/priv/host-x86/isel.c @@ -502,6 +502,66 @@ static HReg iselIntExpr_R_wrk ( ISelEnv* env, IRExpr* e ) return b16; } + if (e->Iex.Binop.op == Iop_F64toI32 || e->Iex.Binop.op == Iop_F64toI16) { + Int sz = e->Iex.Binop.op == Iop_F64toI16 ? 2 : 4; + HReg rf = iselDblExpr(env, e->Iex.Binop.arg2); + HReg rrm = iselIntExpr_R(env, e->Iex.Binop.arg1); + HReg rrm2 = newVRegI(env); + HReg dst = newVRegI(env); + + /* Used several times ... */ + X86AMode* zero_esp = X86AMode_IR(0, hregX86_ESP()); + + /* rf now holds the value to be converted, and rrm holds the + rounding mode value, encoded as per the IRRoundingMode + enum. The first thing to do is set the FPU's rounding + mode accordingly. */ + + /* Create a space, both for the control word messing, and for + the actual store conversion. + /* subl $4, %esp */ + addInstr(env, + X86Instr_Alu32R(Xalu_SUB, X86RMI_Imm(4), hregX86_ESP())); + /* movl %rrm, %rrm2 + andl $3, %rrm2 -- shouldn't be needed; paranoia + shll $10, %rrm2 + orl $0x037F, %rrm2 + movl %rrm2, 0(%esp) + fldcw 0(%esp) + */ + addInstr(env, mk_MOVsd_RR(rrm, rrm2)); + addInstr(env, X86Instr_Alu32R(Xalu_AND, X86RMI_Imm(3), rrm2)); + addInstr(env, X86Instr_Sh32(Xsh_SHL, 10, X86RM_Reg(rrm2))); + addInstr(env, X86Instr_Alu32R(Xalu_OR, X86RMI_Imm(0x037F), rrm2)); + addInstr(env, X86Instr_Alu32M(Xalu_MOV, X86RI_Reg(rrm2), zero_esp)); + addInstr(env, X86Instr_FpLdStCW(True/*load*/, zero_esp)); + + /* gistw/l %rf, 0(%esp) */ + addInstr(env, X86Instr_FpLdStI(False/*store*/, sz, rf, zero_esp)); + + if (sz == 2) { + /* movzwl 0(%esp), %dst */ + addInstr(env, X86Instr_LoadEX(2,False,zero_esp,dst)); + } else { + /* movl 0(%esp), %dst */ + vassert(sz == 4); + addInstr(env, X86Instr_Alu32R( + Xalu_MOV, X86RMI_Mem(zero_esp), dst)); + } + + /* Restore default FPU control. + movl $0x037F, 0(%esp) + fldcw 0(%esp) + */ + addInstr(env, X86Instr_Alu32M(Xalu_MOV, X86RI_Imm(0x037F), zero_esp)); + addInstr(env, X86Instr_FpLdStCW(True/*load*/, zero_esp)); + + /* addl $4, %esp */ + addInstr(env, + X86Instr_Alu32R(Xalu_ADD, X86RMI_Imm(4), hregX86_ESP())); + return dst; + } + break; } @@ -611,39 +671,6 @@ static HReg iselIntExpr_R_wrk ( ISelEnv* env, IRExpr* e ) return dst; } - case Iop_F64toI32: - case Iop_F64toI16: { - Int sz = e->Iex.Unop.op == Iop_F64toI16 ? 2 : 4; - HReg dst = newVRegI(env); - HReg rf = iselDblExpr(env, e->Iex.Unop.arg); - /* subl $sz, %esp */ - addInstr(env, X86Instr_Alu32R(Xalu_SUB, - X86RMI_Imm(sz), - hregX86_ESP())); - /* gistw/l %rf, 0(%esp) */ - addInstr(env, X86Instr_FpLdStI( - False/*store*/, sz, rf, - X86AMode_IR(0, hregX86_ESP()))); - if (sz == 2) { - /* movzwl 0(%esp), %dst */ - addInstr(env, X86Instr_LoadEX( - 2,False, - X86AMode_IR(0, hregX86_ESP()),dst)); - } else { - /* movl 0(%esp), %dst */ - vassert(sz == 4); - addInstr(env, X86Instr_Alu32R(Xalu_MOV, - X86RMI_Mem(X86AMode_IR(0, hregX86_ESP())), - dst)); - } - /* addl $sz, %esp */ - addInstr(env, X86Instr_Alu32R(Xalu_ADD, - X86RMI_Imm(sz), - hregX86_ESP())); - return dst; - } - - case Iop_16to8: case Iop_32to8: case Iop_32to16: @@ -1012,8 +1039,8 @@ static X86CondCode iselCondCode_wrk ( ISelEnv* env, IRExpr* e ) { MatchInfo mi; DECLARE_PATTERN(p_32to1); - DECLARE_PATTERN(p_eq32_literal); - DECLARE_PATTERN(p_ne32_zero); + //DECLARE_PATTERN(p_eq32_literal); + //DECLARE_PATTERN(p_ne32_zero); DECLARE_PATTERN(p_1Uto32_then_32to1); vassert(e); diff --git a/VEX/priv/ir/irdefs.c b/VEX/priv/ir/irdefs.c index 4dfeeafb2a..acda76cead 100644 --- a/VEX/priv/ir/irdefs.c +++ b/VEX/priv/ir/irdefs.c @@ -622,9 +622,12 @@ void typeOfPrimop ( IROp op, IRType* t_dst, IRType* t_arg1, IRType* t_arg2 ) case Iop_AddF64: case Iop_SubF64: case Iop_MulF64: case Iop_DivF64: BINARY(Ity_F64,Ity_F64,Ity_F64); + case Iop_I32toF64: UNARY(Ity_F64,Ity_I32); - case Iop_F64toI32: UNARY(Ity_I32,Ity_F64); - case Iop_F64toI16: UNARY(Ity_I16,Ity_F64); + + case Iop_F64toI32: BINARY(Ity_I32, Ity_I32,Ity_F64); + case Iop_F64toI16: BINARY(Ity_I16, Ity_I32,Ity_F64); + case Iop_F32toF64: UNARY(Ity_F64,Ity_F32); case Iop_F64toF32: UNARY(Ity_F32,Ity_F64); @@ -887,8 +890,14 @@ void tcExpr ( IRBB* bb, IRStmt* stmt, IRExpr* expr, IRType gWordTy ) tcExpr(bb,stmt, expr->Iex.Binop.arg1, gWordTy ); tcExpr(bb,stmt, expr->Iex.Binop.arg2, gWordTy ); typeOfPrimop(expr->Iex.Binop.op, &t_dst, &t_arg1, &t_arg2); - if (t_arg1 == Ity_INVALID || t_arg2 == Ity_INVALID) - sanityCheckFail(bb,stmt,"Iex.Binop: wrong arity op"); + if (t_arg1 == Ity_INVALID || t_arg2 == Ity_INVALID) { + vex_printf(" op name: " ); + ppIROp(expr->Iex.Binop.op); + vex_printf("\n"); + sanityCheckFail(bb,stmt, + "Iex.Binop: wrong arity op\n" + "... name of op precedes BB printout\n"); + } ttarg1 = typeOfIRExpr(tyenv, expr->Iex.Binop.arg1); ttarg2 = typeOfIRExpr(tyenv, expr->Iex.Binop.arg2); if (t_arg1 != ttarg1 || t_arg2 != ttarg2) { diff --git a/VEX/pub/libvex_ir.h b/VEX/pub/libvex_ir.h index 4775077429..15d8f29374 100644 --- a/VEX/pub/libvex_ir.h +++ b/VEX/pub/libvex_ir.h @@ -74,6 +74,25 @@ extern void ppIRTemp ( IRTemp ); /* ------------------ Binary and unary ops ------------------ */ +/* Encoding of rounding modes in Float -> Int conversions. This is + the same as the encoding used by Intel IA32 to indicate x87 + rounding mode. */ +typedef + enum { Irrm_NEAREST=0, Irrm_NegINF=1, Irrm_PosINF=2, Irrm_ZERO=3 } + IRRoundingMode; + +/* Floating point comparison result values, as created by Iop_CmpF64. + This is also derived from what IA32 does. */ +typedef + enum { + Ircr_UN = 0x85, + Ircr_LT = 0x01, + Ircr_GT = 0x00, + Ircr_EQ = 0x80 + } + IRCmpF64Result; + + typedef enum { /* Do not change this ordering. The IR generators @@ -98,15 +117,21 @@ typedef /* Widening multiplies */ Iop_MullS8, Iop_MullS16, Iop_MullS32, Iop_MullU8, Iop_MullU16, Iop_MullU32, + /* Wierdo integer stuff */ Iop_Clz32, /* count leading zeroes */ Iop_Ctz32, /* count trailing zeros */ + /* Ctz32/Clz32 are UNDEFINED when given arguments of zero. + You must ensure they are never given a zero argument. + */ + /* Ordering not important after here. */ Iop_CmpLT32S, Iop_CmpLE32S, Iop_CmpLT32U, Iop_CmpLE32U, /* Division */ + /* TODO: clarify semantics wrt rounding, negative values, whatever */ Iop_DivModU64to32, // :: I64,I32 -> I64 // of which lo half is div and hi half is mod Iop_DivModS64to32, // ditto, signed @@ -131,21 +156,55 @@ typedef Iop_32to1, /* :: Ity_I32 -> Ity_Bit, just select bit[0] */ Iop_1Uto8, /* :: Ity_Bit -> Ity_I8, unsigned widen */ Iop_1Uto32, /* :: Ity_Bit -> Ity_I32, unsigned widen */ - /* FP stuff */ - Iop_AddF64, Iop_SubF64, Iop_MulF64, Iop_DivF64, - Iop_SqrtF64, - /* double <-> int */ - Iop_I32toF64, - Iop_F64toI32, Iop_F64toI16, - /* double <-> float */ + + /* ------ Floating point. We try and be IEEE754 compliant. ------ */ + + /* Simple operations */ + Iop_AddF64, Iop_SubF64, Iop_MulF64, Iop_DivF64, Iop_RemF64, + Iop_SqrtF64, Iop_SinF64, Iop_CosF64, + + /* Comparison, yielding GT/LT/EQ/UN(ordered), as per the following: + 0x85 Unordered + 0x01 LT + 0x00 GT + 0x80 EQ + This just happens to be the Intel encoding. The values + are recorded in the type IRCmpF64Result. + */ + Iop_CmpF64, + + /* int -> double */ + Iop_I32toF64, Iop_I64toF64, + + /* double -> int. These take a first argument :: Ity_I32 + (an IRRoundingMode) which is an indication of the rounding mode, + as per the following encoding: + 00b to nearest (the default) + 01b to -infinity + 10b to +infinity + 11b to zero + This just happens to be the Intel encoding. For reference only, + the PPC encoding is: + 00b to nearest (the default) + 01b to zero + 10b to +infinity + 11b to -infinity + Any PPC -> IR front end will have to translate these PPC + encodings to the standard encodings. + + If one of these conversions gets an out-of-range condition, + or a NaN, as an argument, the result is host-defined. On x86 + the "integer indefinite" value 0x80..00 is produced. + On PPC it is either 0x80..00 or 0x7F..FF depending on the sign + of the argument. + */ + Iop_F64toI64, Iop_F64toI32, Iop_F64toI16, + + /* double <-> float. What does this mean -- does it round? */ Iop_F32toF64, Iop_F64toF32 } IROp; -/* Notes. - Ctz32/Clz32 are UNDEFINED when given arguments of zero. - You must ensure they are never given a zero argument. -*/ extern void ppIROp ( IROp );