From e3d075e375db04bc8bf4c525d17c8032432bb201 Mon Sep 17 00:00:00 2001 From: Mike Miller Date: Wed, 29 Jul 2026 11:34:56 -0700 Subject: [PATCH] Implement fninit fninit (DB E3) is missing from the x87 decode table, so it raises SIGILL, while its immediate neighbour fnclex (DB E2) is there. Reported in #2415, where it kills the Free Pascal compiler at startup: fpc emits fninit in its FPU setup, so nothing compiled with it runs. It resets the control word to 0x037f and clears the status word. Clearing fsw also resets TOP, since TOP is a field of it, which is what empties the register stack; there is no tag word to write because none is modelled. The control word changing means the live rounding mode has to follow it, the same way fldcw does. Verified against real Linux with the same static i386 binary, after dirtying the FPU with a non-default control word and a non-empty stack: before after / real Linux fninit SIGILL runs control word -- 0x037f status word (incl TOP) -- 0x0000 FPU usable afterwards -- yes (The test reads the status word with fnstsw ax rather than fnstsw m16, because the memory form is also unimplemented -- a separate gap.) --- asbestos/gen.c | 1 + emu/decode.h | 1 + emu/fpu.c | 12 ++++++++++++ emu/fpu.h | 1 + 4 files changed, 15 insertions(+) diff --git a/asbestos/gen.c b/asbestos/gen.c index b917ab852d..d1f2e0f8c9 100644 --- a/asbestos/gen.c +++ b/asbestos/gen.c @@ -412,6 +412,7 @@ void helper_rdtsc(struct cpu_state *cpu); #define FLDENV(val,z) h_write(fpu_ldenv, z) #define FSAVE(val,z) h_write(fpu_save, z) #define FRESTORE(val,z) h_write(fpu_restore, z) +#define FINIT() h(fpu_init) #define FCLEX() h(fpu_clex) #define FPOP h(fpu_pop) #define FINCSTP() h(fpu_incstp) diff --git a/emu/decode.h b/emu/decode.h index 00a075fc86..856d9e5c5d 100644 --- a/emu/decode.h +++ b/emu/decode.h @@ -1084,6 +1084,7 @@ __no_instrument DECODER_RET glue(DECODER_NAME, OP_SIZE)(DECODER_ARGS) { case 0xd976: TRACE("fsin"); FSIN(); break; case 0xd977: TRACE("fcos"); FCOS(); break; case 0xdb42: TRACE("fnclex"); FCLEX(); break; + case 0xdb43: TRACE("fninit"); FINIT(); break; case 0xde31: TRACE("fcompp"); FCOM(); FPOP; FPOP; break; case 0xdf40: TRACE("fnstsw ax"); FSTSW(reg_a); break; default: TRACE("undefined"); UNDEFINED; diff --git a/emu/fpu.c b/emu/fpu.c index d523e30fd0..78c14fad0e 100644 --- a/emu/fpu.c +++ b/emu/fpu.c @@ -377,6 +377,18 @@ void fpu_restore32(struct cpu_state *cpu, struct fpu_state32 *state) { memcpy(&ST(i), state->regs[i], 10); } +// FNINIT: control word back to 0x037f (all exceptions masked, round to +// nearest, extended precision) and the status word cleared. Clearing fsw also +// resets TOP, since TOP is a field of it, which is what empties the register +// stack; there is no tag word to write because we do not model one. The +// control word changing means the live rounding mode has to follow it, the +// same way fldcw does. +void fpu_init(struct cpu_state *cpu) { + cpu->fcw = 0x037f; + cpu->fsw = 0; + f80_rounding_mode = cpu->rc; +} + void fpu_clex(struct cpu_state *cpu) { cpu->pe = cpu->ue = cpu->oe = cpu->ze = cpu->de = cpu->ie = cpu->es = cpu->sf = cpu->b = 0; } diff --git a/emu/fpu.h b/emu/fpu.h index 7cdf6349e4..06c5890291 100644 --- a/emu/fpu.h +++ b/emu/fpu.h @@ -119,6 +119,7 @@ void fpu_stenv32(struct cpu_state *cpu, struct fpu_env32 *env); void fpu_ldenv32(struct cpu_state *cpu, struct fpu_env32 *env); void fpu_save32(struct cpu_state *cpu, struct fpu_state32 *state); void fpu_restore32(struct cpu_state *cpu, struct fpu_state32 *state); +void fpu_init(struct cpu_state *cpu); void fpu_clex(struct cpu_state *cpu); #endif