Context
I'm trying to change the FPU rounding mode behaviour in a running process, using a signal handler. Although I would prefer a portable solution, the platform I'm targetting in priority is Linux/x86.
Using fesetround()
seemed like a good solution, except it doesn't have any effect after the signal handler returns. As far as I understand, at each context change, the Linux kernel saves the hardware context of the currently running process, and restores the context of the process scheduled to run.
Although this context handling normally happens in kernel space, I understand from various resources that in the case of signals handling, this context is stored in user space and can be accessed. I tried to use this to modify the FPU registers and set the desired rounding mode.
Attempted solution & problems
Below is the code I use to do this. init()
is called at the beginning of the program to setup the signal handler, and a USR1
signal is sent to trigger the rounding mode switch.
All this kind of works insofar as calling fegetround()
after the signal handler execution correctly returns FE_TOWARDZERO
. However, computation results don't seem to be affected.
Calling fesetround( fegetround() )
after the signal handler execution however seems to alter the FPU configuration and causes computation results to actually change. All this seems to hint at another layer indirection, as though I correctly altered the hardware context, but it isn't coherent with the actual FPU state.
Question
Can anybody explain why this isn't working and possibly what could be attempted to correct the problem?
#include <signal.h>
#include <fenv.h>
#pragma STDC FENV_ACCESS ON
void changeRoundingMode (ucontext_t * ucontext)
{
/* This doesn't seem to work since the hardware context is restored
to its old value immediately after the signal handler return */
fesetround (FE_TOWARDZERO);
/* try and modify the context */
{
/* http://www.website.masmforum.com/tutorials/fptute/fpuchap1.htm#cword
Rounding mode is encoded in bits 10 & 11 (RC = Rounding Control field) */
__uint16_t cwd = ucontext->uc_mcontext.fpregs->cwd;
/* Clear bits 10 & 11 */
const __uint16_t clearRC = 0xf3ff;
cwd &= clearRC;
/* And set them to the desired value
11 -> TOWARDZERO in this case */
cwd += 0x0c00;
ucontext->uc_mcontext.fpregs->cwd = cwd;
}
}
void signalHandler (int signum, siginfo_t * siginfo, void * ucontext)
{
changeRoundingMode (ucontext);
}
void init ()
{
struct sigaction new_action, old_action;
new_action.sa_sigaction = verrou_signalHandler;
sigemptyset (&new_action.sa_mask);
new_action.sa_flags = SA_RESTART | SA_SIGINFO;
sigaction (SIGUSR1, 0, &old_action);
if (old_action.sa_handler != SIG_IGN)
sigaction (SIGUSR1, &new_action, 0);
}