Skip to content

Commit 88d67a2

Browse files
committed
Lifter: fsincos, fxam, fcmov, xlat, MXCSR; x87 NaN results as on the FPU
- fsincos (the D3DX rotation helpers), fxam, fcmovCC, xlatb and ldmxcsr/stmxcsr were trapped as unsupported and did nothing: 33 sites in the European build, 32 in the North American one. fcmovCC reads EFLAGS, so the flag liveness counts its condition's flags as used. MXCSR is kept in the CPU state (power-on 0x1F80) but not applied. - Two NaN operands: the x87 picks a quiet one over a signaling one, otherwise the larger significand, and always returns it quiet (SDM vol. 1, table 4-7); ARM took the first operand. SSE keeps the first operand's NaN. - isatest: 15 new cases (474 pass).
1 parent 43200c7 commit 88d67a2

5 files changed

Lines changed: 102 additions & 9 deletions

File tree

‎runtime/cpu.h‎

Lines changed: 45 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ typedef struct CPU {
4646
/* MMX (not shared with x87, kept separately) */
4747
uint64_t mm[8];
4848
/* SSE */
49+
uint32_t mxcsr; /* SSE control/status: only stored and read back (ldmxcsr/stmxcsr) */
4950
union { uint8_t b[16]; float f[4]; double d[2]; uint32_t u32[4]; uint64_t u64[2]; } xmm[8];
5051
} CPU;
5152

@@ -150,6 +151,21 @@ static inline uint32_t parity8(uint32_t v){ v&=0xff; v^=v>>4; v^=v>>2; v^=v>>1;
150151
#define FSW_C1 (1u<<9)
151152
#define FSW_C2 (1u<<10)
152153
#define FSW_C3 (1u<<14)
154+
/* FXAM: the class of v in C3/C2/C0, its sign in C1. The x87 registers carry no
155+
* tags here, so "empty" (C3+C0) is never answered; a value counts as denormal
156+
* by the double's range. */
157+
static inline uint16_t x87_class_bits(double v){
158+
uint16_t k;
159+
switch (fpclassify(v)){
160+
case FP_NAN: k = FSW_C0; break;
161+
case FP_INFINITE: k = FSW_C2 | FSW_C0; break;
162+
case FP_ZERO: k = FSW_C3; break;
163+
case FP_SUBNORMAL: k = FSW_C3 | FSW_C2; break;
164+
default: k = FSW_C2; break;
165+
}
166+
return signbit(v) ? (uint16_t)(k | FSW_C1) : k;
167+
}
168+
#define X87_EXAMINE(v) (c->fsw = (uint16_t)((c->fsw & (uint16_t)~(FSW_C0|FSW_C1|FSW_C2|FSW_C3)) | x87_class_bits(v)))
153169
/* fnstsw: on real hardware the TOP field (bits 11-13) is read too. */
154170
static inline uint16_t fsw_get(const struct CPU *c){
155171
return (uint16_t)((c->fsw & (uint16_t)~0x3800u) | (uint16_t)((c->top & 7u) << 11));
@@ -263,12 +279,30 @@ static inline double x87_round(uint16_t fcw, double v){
263279
* NaN, it must be fixed separately. If one of the inputs is a NaN, that one
264280
* propagates. */
265281
#define X87_INDEF (-__builtin_nan(""))
266-
static inline double x87_r2(double a, double b, double r){
267-
if (r == r) return r;
268-
if (a != a) return a;
269-
if (b != b) return b;
282+
/* A NaN operand of an x87 operation (Intel SDM vol. 1, table 4-7): the result is
283+
* that NaN made quiet. With two NaNs, a quiet one beats a signaling one, and of
284+
* two of the same kind the larger significand wins (a, the destination, on a tie).
285+
* ARM simply takes the first operand, so a two-NaN result differed from Windows.
286+
* Out of line: the NaN path is cold and x87_r2 is inlined at every operation. */
287+
static inline double x87_quiet(double v){
288+
uint64_t u; memcpy(&u, &v, 8); u |= 1ull << 51; memcpy(&v, &u, 8); return v;
289+
}
290+
static __attribute__((noinline, cold, unused)) double x87_nan2(double a, double b){
291+
if (a != a && b != b){
292+
uint64_t ua, ub; memcpy(&ua, &a, 8); memcpy(&ub, &b, 8);
293+
unsigned qa = (unsigned)(ua >> 51) & 1u, qb = (unsigned)(ub >> 51) & 1u;
294+
if (qa != qb) return qa ? a : b;
295+
uint64_t sa = ua & 0x0007FFFFFFFFFFFFull, sb = ub & 0x0007FFFFFFFFFFFFull;
296+
return x87_quiet(sb > sa ? b : a);
297+
}
298+
if (a != a) return x87_quiet(a);
299+
if (b != b) return x87_quiet(b);
270300
return X87_INDEF;
271301
}
302+
static inline double x87_r2(double a, double b, double r){
303+
if (__builtin_expect(r == r, 1)) return r;
304+
return x87_nan2(a, b);
305+
}
272306
static inline double x87_r1(double a, double r){
273307
if (r == r) return r;
274308
if (a != a) return a;
@@ -285,7 +319,13 @@ static inline float sse_r1f(float a, float r){
285319
if (a != a) return a;
286320
return (float)X87_INDEF;
287321
}
288-
static inline double sse_r2d(double a, double b, double r){ return x87_r2(a,b,r); }
322+
/* SSE: the first operand's NaN wins whatever the second is (made quiet) */
323+
static inline double sse_r2d(double a, double b, double r){
324+
if (r == r) return r;
325+
if (a != a) return x87_quiet(a);
326+
if (b != b) return x87_quiet(b);
327+
return X87_INDEF;
328+
}
289329
static inline double sse_r1d(double a, double r){ return x87_r1(a,r); }
290330

291331
/* Out of range / NaN -> "integer indefinite" (the most negative value). */

‎runtime/win32_ext.c‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -400,7 +400,7 @@ static void *guest_thread_main(void *p){
400400
mem_commit(stack_lo, GTHREAD_STACK);
401401
memset(g_mem + stack_lo, 0, GTHREAD_STACK); /* a recycled slot starts clean */
402402
cpu.esp = stack_hi - 0x1000u;
403-
cpu.top = 0; cpu.fcw = 0x037f;
403+
cpu.top = 0; cpu.fcw = 0x037f; cpu.mxcsr = 0x1F80u;
404404
cpu.fs_base = t->teb; cpu.gs_base = t->teb;
405405
/* TEB */
406406
MR32(t->teb+0x00)=0xFFFFFFFFu; MR32(t->teb+0x04)=stack_hi; MR32(t->teb+0x08)=stack_lo;
@@ -466,7 +466,7 @@ static void *timer_thread_main(void *p){
466466
teb = 0x00C60000u;
467467
mem_commit(stack_lo, GTHREAD_STACK);
468468
mem_commit(teb, 0x2000);
469-
cpu.top = 0; cpu.fcw = 0x037f;
469+
cpu.top = 0; cpu.fcw = 0x037f; cpu.mxcsr = 0x1F80u;
470470
cpu.fs_base = teb; cpu.gs_base = teb;
471471
MR32(teb+0x00)=0xFFFFFFFFu; MR32(teb+0x04)=stack_hi; MR32(teb+0x08)=stack_lo;
472472
MR32(teb+0x18)=teb; MR32(teb+0x24)=999u;

‎tests/boot.c‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -175,7 +175,7 @@ int main(int argc,char**argv){
175175
{ extern CPU *g_cur_cpu; g_cur_cpu = &cpu; }
176176
memset(&cpu,0,sizeof cpu);
177177
cpu.esp = STACK_HI - 0x1000u;
178-
cpu.top = 0; cpu.fcw = 0x037f;
178+
cpu.top = 0; cpu.fcw = 0x037f; cpu.mxcsr = 0x1F80u;
179179
cpu.fs_base = TEB_BASE;
180180
cpu.gs_base = TEB_BASE;
181181

‎tools/isatest.py‎

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -343,6 +343,41 @@ def _rc(name, cw, val, want):
343343
{}, mem={FC+0x80:1,FC+0x84:2,FC+0x88:3,FC+0x8C:4},
344344
fmem={FC+0x90:3, FC+0x94:4})
345345

346+
# ---------------- x87 / SSE control instructions the lifter used to trap ----------------
347+
FX = 0x00C74000
348+
T("fsincos 1.0: cos on top, sin below",
349+
[0xD9,0x05]+a32(FX)+[0xD9,0xFB]+[0xD9,0x1D]+a32(FX+4)+[0xD9,0x1D]+a32(FX+8),
350+
{}, memf={FX:1.0}, fmemf={FX+4:0.5403023058681398, FX+8:0.8414709848078965})
351+
T("fxam 2.0 -> normal (C2)", [0xD9,0x05]+a32(FX+0x10)+[0xD9,0xE5,0xDF,0xE0,0xDD,0xD8],
352+
{}, memf={FX+0x10:2.0}, expect={'ah_and':(0x47,0x04)})
353+
T("fxam -0.0 -> zero, sign (C3,C1)", [0xD9,0x05]+a32(FX+0x14)+[0xD9,0xE5,0xDF,0xE0,0xDD,0xD8],
354+
{}, mem={FX+0x14:0x80000000}, expect={'ah_and':(0x47,0x42)})
355+
T("fxam +inf -> infinity (C2,C0)", [0xD9,0x05]+a32(FX+0x18)+[0xD9,0xE5,0xDF,0xE0,0xDD,0xD8],
356+
{}, mem={FX+0x18:0x7F800000}, expect={'ah_and':(0x47,0x05)})
357+
# fld 7, fld 3 (ST0=3, ST1=7); a flag setter; fcmovCC st0,st1; fstp result; pop
358+
def _fcm(name, flagop, op, base, want):
359+
T(name, [0xD9,0x05]+a32(base)+[0xD9,0x05]+a32(base+4)+flagop+op+[0xD9,0x1D]+a32(base+8)+[0xDD,0xD8],
360+
{}, memf={base:7.0, base+4:3.0}, fmemf={base+8:want})
361+
_fcm("fcmovbe, CF=1 -> copies", [0xF9], [0xDA,0xD1], FX+0x20, 7.0)
362+
_fcm("fcmovnbe, CF=0 ZF=0 -> copies", [0x83,0xC8,0x01], [0xDB,0xD1], FX+0x30, 7.0)
363+
_fcm("fcmove, ZF=0 -> keeps", [0x83,0xC8,0x01], [0xDA,0xC9], FX+0x40, 3.0)
364+
_fcm("fcmovnb, CF=1 -> keeps", [0xF9], [0xDB,0xC1], FX+0x50, 3.0)
365+
T("xlatb: AL = [EBX+AL]", [0xD7], {'ebx':FX+0x60, 'eax':0xAABBCC02}, mem={FX+0x60:0x44332211},
366+
expect={'eax':0xAABBCC33})
367+
T("stmxcsr: power-on value", [0x0F,0xAE,0x1D]+a32(FX+0x70), {}, fmem={FX+0x70:0x1F80})
368+
T("ldmxcsr then stmxcsr", [0x0F,0xAE,0x15]+a32(FX+0x74)+[0x0F,0xAE,0x1D]+a32(FX+0x78),
369+
{}, mem={FX+0x74:0x00009FC0}, fmem={FX+0x78:0x00009FC0})
370+
371+
# x87 NaN results (SDM vol. 1, table 4-7): fld qword a; fadd qword b; fstp qword r
372+
def _nan(name, a, b, want, base):
373+
T(name, [0xDD,0x05]+a32(base)+[0xDC,0x05]+a32(base+8)+[0xDD,0x1D]+a32(base+16),
374+
{}, mem={base:a & 0xffffffff, base+4:a >> 32, base+8:b & 0xffffffff, base+12:b >> 32},
375+
fmem={base+16:want & 0xffffffff, base+20:want >> 32})
376+
_nan("x87 NaN: two QNaNs, larger second", 0x7FF8000000000001, 0x7FF8000000000100, 0x7FF8000000000100, FX+0x80)
377+
_nan("x87 NaN: two QNaNs, larger first", 0xFFF8000000000300, 0x7FF8000000000002, 0xFFF8000000000300, FX+0xA0)
378+
_nan("x87 NaN: QNaN beats a larger SNaN", 0x7FF7FFFFFFFFFFFF, 0x7FF8000000000001, 0x7FF8000000000001, FX+0xC0)
379+
_nan("x87 NaN: lone SNaN comes back quiet", 0x7FF0000000000100, 0x3FF0000000000000, 0x7FF8000000000100, FX+0xE0)
380+
346381
# ===================== GENERATOR / RUNNER =====================
347382
BASE = 0x00401000
348383
def build():
@@ -383,7 +418,7 @@ def gen_c(fns, body):
383418
if fns[i] is None: continue
384419
nm = t['name'].replace('"','\\"')
385420
o.append(f' /* --- {i}: {nm} --- */')
386-
o.append(' { memset(&cpu,0,sizeof cpu); cpu.esp=STK; cpu.top=0; cpu.fcw=0x037f;')
421+
o.append(' { memset(&cpu,0,sizeof cpu); cpu.esp=STK; cpu.top=0; cpu.fcw=0x037f; cpu.mxcsr=0x1F80u;')
387422
for r,v in t['setup'].items():
388423
o.append(f' cpu.{r} = 0x{v & 0xffffffff:x}u;')
389424
for a,v in t['mem'].items():

‎tools/lift.py‎

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,12 +90,19 @@ def _shift_count(i, base):
9090
# those flags as dead above and skip the real computation -> a stale value.
9191
# Only the fcomi/fucomi family really writes EFLAGS (the FCOMI macro all 6).
9292
_FCOMI = ('fcomi','fcomip','fucomi','fucomip')
93+
# fcmovCC is the one x87 instruction that READS EFLAGS: ST(0) = ST(i) when the
94+
# condition (the jcc of the same name) holds. Its flags must count as used.
95+
_X87_CMOV = {'fcmovb':'b', 'fcmovnb':'ae', 'fcmove':'e', 'fcmovne':'ne',
96+
'fcmovbe':'be', 'fcmovnbe':'a', 'fcmovu':'p', 'fcmovnu':'np'}
97+
_CC_READS = {'b':F_CF, 'ae':F_CF, 'e':F_ZF, 'ne':F_ZF, 'be':F_CF|F_ZF, 'a':F_CF|F_ZF,
98+
'p':F_PF, 'np':F_PF}
9399

94100
def _flag_defuse(i):
95101
if i.mnemonic in _OPAQUE:
96102
return 0, F_ALL
97103
base = i.mnemonic.split()[-1] # "repe cmpsb" -> "cmpsb"
98104
if base[:1] == 'f' or X86_GRP_FPU in i.groups:
105+
if base in _X87_CMOV: return 0, _CC_READS[_X87_CMOV[base]]
99106
return (F_ALL, 0) if base in _FCOMI else (0, 0)
100107
ef = getattr(i, 'eflags', 0) or 0
101108
d = u = 0
@@ -684,6 +691,12 @@ def imem(k):
684691
if m=='fsqrt': return ["{ double _a=ST(0); ST(0)=x87_r1(_a, sqrt(_a)); }"]
685692
if m=='fsin': return ["ST(0) = sin(ST(0));"]
686693
if m=='fcos': return ["ST(0) = cos(ST(0));"]
694+
# fsincos: x in ST(0) -> ST(1) = sin x, ST(0) = cos x (the D3DX rotation helpers)
695+
if m=='fsincos': return ["FPUSH(ST(0));", "ST(1) = sin(ST(1));", "ST(0) = cos(ST(0));"]
696+
if m=='fxam': return ["X87_EXAMINE(ST(0));"]
697+
if m in _X87_CMOV:
698+
src = sti(len(o)-1) if o and is_st(len(o)-1) else 1
699+
return [f"if ({CC[_X87_CMOV[m]]}) ST(0) = ST({src});"]
687700
if m=='fptan': return ["ST(0) = tan(ST(0));","FPUSH(1.0);"]
688701
if m=='fpatan': return ["ST(1) = atan2(ST(1), ST(0));","FPOP();"]
689702
if m=='fyl2x': return ["ST(1) = ST(1) * log2(ST(0));","FPOP();"]
@@ -818,6 +831,8 @@ def imem(k):
818831
if m=='bswap': return [W(0,f"__builtin_bswap32({E(0)})")]
819832
if m=='sahf': return ["{ uint32_t _f=(c->eax>>8)&0xffu; c->cf=_f&1; c->pf=(_f>>2)&1; c->af=(_f>>4)&1; c->zf=(_f>>6)&1; c->sf=(_f>>7)&1; }"]
820833
if m=='lahf': return ["c->eax = (c->eax & 0xffff00ffu) | ((c->sf<<7|c->zf<<6|c->af<<4|c->pf<<2|2u|c->cf)<<8);"]
834+
if m in ('xlatb','xlat'): # AL = the byte at EBX + AL (a table lookup)
835+
return ["{ uint32_t _t = c->ebx + (c->eax & 0xffu); c->eax = (c->eax & 0xffffff00u) | MR8(_t); }"]
821836
if m=='cpuid': return ["cpuid_shim(c);"]
822837
if m=='rdtsc': return ["{ uint64_t _t=rdtsc_shim(); c->eax=(uint32_t)_t; c->edx=(uint32_t)(_t>>32); }"]
823838
if m in ('clc',): return ["c->cf=0;"]
@@ -870,6 +885,9 @@ def is_gp(k): return o[k].type==X86_OP_REG and o[k].reg in R32
870885
def is_mem(k): return o[k].type==X86_OP_MEM
871886

872887
if m=='emms': return ["/* emms */"]
888+
# MXCSR is kept (c->mxcsr) but not applied: SSE arithmetic rounds to nearest
889+
if m=='stmxcsr': return [f"MR32{self.ea(o[0].mem)} = c->mxcsr;"]
890+
if m=='ldmxcsr': return [f"c->mxcsr = MR32{self.ea(o[0].mem)};"]
873891
if m in ('movq','movd'):
874892
w = 8 if m=='movq' else 4
875893
T = 'uint64_t' if w==8 else 'uint32_t'

0 commit comments

Comments
 (0)