/* EIP */
#define OFFB_EIP (12*4)
-/* FPU. For now, just simulate 8 64-bit registers and the reg-stack
- top pointer, of which only the least significant three bits are
- relevant.
+/* FPU. For now, just simulate 8 64-bit registers, their tags, and
+ the reg-stack top pointer, of which only the least significant
+ three bits are relevant.
The model is:
- F0 .. F7 are the 8 registers. ftop[2:0] contains the
+ F0 .. F7 are the 8 registers. FTOP[2:0] contains the
index of the current 'stack top' -- pretty meaningless, but
- still.
+ still. FTOP is a 32-bit value.
When a value is pushed onto the stack, ftop is first replaced by
(ftop-1) & 7, and then F[ftop] is assigned the value.
In general, a reference to a register ST(i) actually references
F[ (ftop+i) & 7 ].
- There should be an array of 8 booleans corresponding to F0 .. F7,
- indicating whether the corresponding F reg contains a value or not.
+ FTAG0 .. FTAG0+7 are the tags. Each is a byte, zero means empty,
+ non-zero means non-empty.
- A read of an F reg marked empty, for any reason, elicits a stack
- underflow fault.
+ The general rule appears to be that a read or modify of a register
+ gets a stack underflow fault if the register is empty. A write of
+ a register (only a write, not a modify) gets a stack overflow fault
+ if the register is full. Note that "over" vs "under" is pretty
+ meaningless since the FP stack pointer can move around arbitrarily,
+ so it's really just two different kinds of exceptions:
+ register-empty and register full.
+
+ Naturally Intel (in its infinite wisdom) has seen fit to throw in
+ some ad-hoc inconsistencies to the fault-generation rules of the
+ above para, just to complicate everything. Known inconsistencies:
+
+ * fxam can read a register in any state without taking an underflow
+ fault.
+
+ * fst from st(0) to st(i) does not take an overflow fault even if the
+ destination is already full.
- A load from memory into an F reg marked full elicits a stack overflow
- fault. This appears to be the only way a stack overflow fault can
- happen.
*/
-#define OFFB_F0 (13*4)
-#define OFFB_F1 (15*4)
-#define OFFB_F2 (17*4)
-#define OFFB_F3 (19*4)
-#define OFFB_F4 (21*4)
-#define OFFB_F5 (23*4)
-#define OFFB_F6 (25*4)
-#define OFFB_F7 (27*4)
-#define OFFB_FTOP (29*4)
+#define OFFB_FTOP (13*4)
+#define OFFB_F0 (14*4)
+#define OFFB_F1 (16*4)
+#define OFFB_F2 (18*4)
+#define OFFB_F3 (20*4)
+#define OFFB_F4 (22*4)
+#define OFFB_F5 (24*4)
+#define OFFB_F6 (26*4)
+#define OFFB_F7 (28*4)
+#define OFFB_FTAG0 (30*4) // up to 30*4 + 7
/* Don't forget to keep this up to date. */
-#define SIZEOF_X86H_STATE OFFB_FTOP
+#define SIZEOF_X86H_STATE (OFFB_FTAG0 + 8)
/*--- x87 floating point insns. ---*/
/*------------------------------------------------------------*/
-/* Get/set the top-of-stack pointer. */
+/* --- Helper functions for dealing with the register stack. --- */
+
+/* --- Produce an IRExpr* denoting a 64-bit NaN. --- */
+
+static IRExpr* mkNaN64 ( void )
+{
+ return IRExpr_Const(IRConst_NaN64());
+}
+
+/* --------- Get/set the top-of-stack pointer. --------- */
static IRExpr* get_ftop ( void )
{
return IRExpr_Get( OFFB_FTOP, Ity_I32 );
}
-static IRStmt* put_ftop ( IRExpr* e )
+static void put_ftop ( IRExpr* e )
+{
+ stmt( IRStmt_Put( OFFB_FTOP, e ) );
+}
+
+
+/* --------- Get/set FP register tag bytes. --------- */
+
+/* Given i, generate an expression which is the offset in the guest
+ state of ST(i)'s tag byte, considering the current value of FTOP. The
+ generated expression is:
+
+ ((Get(OFFB_FTOP) + i) & 7) + OFFB_FTAGO
+*/
+static IRExpr* off_ST_TAG ( Int i )
+{
+ vassert(i >= 0 && i <= 7);
+ return
+ binop(Iop_Add32,
+ binop(Iop_And32,
+ binop(Iop_Add32, get_ftop(), mkU32(i)),
+ mkU32(7)),
+ mkU32(OFFB_FTAG0)
+ );
+}
+
+/* Given i, and some expression e, generate 'ST_TAG(i) = e'. */
+
+static void put_ST_TAG ( Int i, IRExpr* value )
+{
+ vassert(typeOfIRExpr(irbb->tyenv, value) == Ity_I8);
+ stmt(
+ IRStmt_PutI( off_ST_TAG(i), value, OFFB_FTAG0, OFFB_FTAG0 +7 )
+ );
+}
+
+/* Given i, generate an expression yielding 'ST_TAG(i)'. This will be
+ zero to indicate "Empty" and nonzero and indicate "NonEmpty". */
+
+static IRExpr* get_ST_TAG ( Int i )
{
- return IRStmt_Put( OFFB_FTOP, e );
+ return
+ IRExpr_GetI( off_ST_TAG(i), Ity_I8, OFFB_FTAG0, OFFB_FTAG0 +7 );
}
+
+/* --------- Get/set FP registers. --------- */
+
/* Given i, generate an expression which is the offset in the guest
- state of ST(i), considering the current value of FTOP. */
+ state of ST(i), considering the current value of FTOP. The
+ generated expression is:
+ (((Get(OFFB_FTOP) + i) & 7) << 3) + OFFB_FO
+*/
static IRExpr* off_ST ( Int i )
{
vassert(i >= 0 && i <= 7);
return
binop(Iop_Add32,
- binop(Iop_Mul32,
+ binop(Iop_Shl32,
binop(Iop_And32,
binop(Iop_Add32, get_ftop(), mkU32(i)),
mkU32(7)),
- mkU32(8)),
+ mkU8(3)),
mkU32(OFFB_F0)
);
}
-/* Given i, and some expression e, generate 'ST(i) = e'. */
-static IRStmt* put_ST ( Int i, IRExpr* value )
+/* Given i, and some expression e, emit 'ST(i) = e'
+and set the register's tag to indicate the register is full.
+The previous state of the register is not checked. */
+
+static void put_ST_UNCHECKED ( Int i, IRExpr* value )
{
- return
- IRStmt_PutI( off_ST(i), value, OFFB_F0, OFFB_F7+8-1 );
+ stmt( IRStmt_PutI( off_ST(i), value, OFFB_F0, OFFB_F7+8-1 ) );
+ /* Mark the register as in-use. */
+ put_ST_TAG(i, mkU8(1));
}
+/* Given i, and some expression e, emit
+ ST(i) = is_full(i) ? NaN : e
+ and set the tag accordingly.
+*/
+
+static void put_ST ( Int i, IRExpr* value )
+{
+ put_ST_UNCHECKED( i,
+ IRExpr_Mux0X(get_ST_TAG(i),
+ /* 0 means empty */
+ value,
+ /* non-0 means full */
+ mkNaN64()
+ )
+ );
+}
+
+
/* Given i, generate an expression yielding 'ST(i)'. */
-static IRExpr* get_ST ( Int i )
+static IRExpr* get_ST_UNCHECKED ( Int i )
{
return
IRExpr_GetI( off_ST(i), Ity_F64, OFFB_F0, OFFB_F7+8-1 );
}
-/* Adjust FTOP downwards by one register. */
+/* Given i, generate an expression yielding
+ is_full(i) ? ST(i) : NaN
+*/
-static IRStmt* fp_push ( void )
+static IRExpr* get_ST ( Int i )
{
return
- put_ftop(
- binop(Iop_And32,
- binop(Iop_Sub32, get_ftop(), mkU32(1)),
- mkU32(7))
- );
+ IRExpr_Mux0X( get_ST_TAG(i),
+ /* 0 means empty */
+ mkNaN64(),
+ /* non-0 means full */
+ get_ST_UNCHECKED(i));
}
-/* Adjust FTOP upwards by one register. */
-static IRStmt* fp_pop ( void )
+/* Adjust FTOP downwards by one register. */
+
+static void fp_push ( void )
{
- return
- put_ftop(
- binop(Iop_And32,
- binop(Iop_Add32, get_ftop(), mkU32(1)),
- mkU32(7))
- );
+ put_ftop(
+ binop(Iop_And32,
+ binop(Iop_Sub32, get_ftop(), mkU32(1)),
+ mkU32(7))
+ );
}
+/* Adjust FTOP upwards by one register, and mark the vacated register
+ as empty. */
+
+static void fp_pop ( void )
+{
+ put_ST_TAG(0, mkU8(1));
+ put_ftop(
+ binop(Iop_And32,
+ binop(Iop_Add32, get_ftop(), mkU32(1)),
+ mkU32(7))
+ );
+}
+
+/* ------------------------------------------------------- */
+/* Given all that stack-mangling junk, we can now go ahead
+ and describe FP instructions.
+*/
+
+/* ST(0) = ST(0) `op` mem64(addr) (when dbl==True)
+ Need to check ST(0)'s tag on read, but not on write.
+*/
static
void fp_do_op_mem_ST_0 ( IRTemp addr, UChar* op_txt, UChar* dis_buf,
IROp op, Bool dbl )
{
DIP("f%s%c %s", op_txt, dbl?'l':'s', dis_buf);
if (dbl) {
- stmt( put_ST(0, binop(op, get_ST(0), loadLE(Ity_F64,mkexpr(addr)))));
+ put_ST_UNCHECKED(0, binop(op, get_ST(0), loadLE(Ity_F64,mkexpr(addr))));
} else {
vassert(0);
}
DIP("fld %%st(%d)\n", r_src);
t1 = newTemp(Ity_F64);
assign(t1, get_ST(r_src));
- stmt( fp_push() );
- stmt( put_ST(0, mkexpr(t1)) );
+ fp_push();
+ put_ST(0, mkexpr(t1));
break;
case 0xEE: /* FLDZ */
DIP("fldz");
- stmt( fp_push() );
- stmt( put_ST(0, IRExpr_Const(IRConst_F64(0.0))) );
+ fp_push();
+ put_ST(0, IRExpr_Const(IRConst_F64(0.0)));
break;
default:
delta += len;
switch (gregOfRM(modrm)) {
- case 1: /* FIMUL m32int */
+ case 1: /* FIMUL m32int */ /* ST(0) *= m32int */
DIP("fimull %s", dis_buf);
- stmt( put_ST(0,
- binop(Iop_MulF64,
- get_ST(0),
- unop(Iop_I64toF64,
- unop(Iop_32Sto64,
- loadLE(Ity_I32, mkexpr(addr)))))));
+ put_ST_UNCHECKED(0,
+ binop(Iop_MulF64,
+ get_ST(0),
+ unop(Iop_I64toF64,
+ unop(Iop_32Sto64,
+ loadLE(Ity_I32, mkexpr(addr))))));
break;
default:
case 0: /* FLD double-real */
DIP("fldD %s\n", dis_buf);
- stmt( fp_push() );
- stmt( put_ST(0, IRExpr_LDle(Ity_F64, mkexpr(addr))) );
+ fp_push();
+ put_ST(0, IRExpr_LDle(Ity_F64, mkexpr(addr)));
break;
#if 0
case 3: /* FSTP double-real */
DIP("fstpD %s", dis_buf);
storeLE(mkexpr(addr), get_ST(0));
- stmt( fp_pop() );
+ fp_pop();
break;
default:
case 0xD8 ... 0xDF: /* FSTP %st(0),%st(?) */
r_dst = (UInt)modrm - 0xD8;
DIP("fstp %%st(0),%%st(%d)\n", r_dst);
- stmt( put_ST(r_dst, get_ST(0)) );
- stmt( fp_pop() );
+ /* P4 manual says: "If the destination operand is a
+ non-empty register, the invalid-operation exception
+ is not generated. Hence put_ST_UNCHECKED. */
+ put_ST_UNCHECKED(r_dst, get_ST(0));
+ fp_pop();
break;
default:
goto decode_fail;
void ppIRConst ( IRConst* con )
{
switch (con->tag) {
- case Ico_Bit: vex_printf( "%d:Bit", con->Ico.Bit ? 1 : 0); break;
- case Ico_U8: vex_printf( "0x%x:I8", (UInt)(con->Ico.U8)); break;
- case Ico_U16: vex_printf( "0x%x:I16", (UInt)(con->Ico.U16)); break;
- case Ico_U32: vex_printf( "0x%x:I32", (UInt)(con->Ico.U32)); break;
- case Ico_U64: vex_printf( "0x%llx:I64", (ULong)(con->Ico.U64)); break;
- case Ico_F64: vex_printf("(f64 value)"); break;
+ case Ico_Bit: vex_printf( "%d:Bit", con->Ico.Bit ? 1 : 0); break;
+ case Ico_U8: vex_printf( "0x%x:I8", (UInt)(con->Ico.U8)); break;
+ case Ico_U16: vex_printf( "0x%x:I16", (UInt)(con->Ico.U16)); break;
+ case Ico_U32: vex_printf( "0x%x:I32", (UInt)(con->Ico.U32)); break;
+ case Ico_U64: vex_printf( "0x%llx:I64", (ULong)(con->Ico.U64)); break;
+ case Ico_F64: vex_printf( "(f64 value)"); break;
+ case Ico_NaN64: vex_printf( "NaN:F64"); break;
default: vpanic("ppIRConst");
}
}
c->Ico.F64 = f64;
return c;
}
+IRConst* IRConst_NaN64 ( void )
+{
+ IRConst* c = LibVEX_Alloc(sizeof(IRConst));
+ c->tag = Ico_NaN64;
+ return c;
+}
+
/* Constructors -- IRExpr */
IRType typeOfIRConst ( IRConst* con )
{
switch (con->tag) {
- case Ico_Bit: return Ity_Bit;
- case Ico_U8: return Ity_I8;
- case Ico_U16: return Ity_I16;
- case Ico_U32: return Ity_I32;
- case Ico_U64: return Ity_I64;
- case Ico_F64: return Ity_F64;
+ case Ico_Bit: return Ity_Bit;
+ case Ico_U8: return Ity_I8;
+ case Ico_U16: return Ity_I16;
+ case Ico_U32: return Ity_I32;
+ case Ico_U64: return Ity_I64;
+ case Ico_F64: return Ity_F64;
+ case Ico_NaN64: return Ity_F64;
default: vpanic("typeOfIRConst");
}
}