Re: Expected behavior when returning from a SIGFPE handler

2021-05-27 Thread Jason Thorpe


> On May 27, 2021, at 6:17 AM, Jason Thorpe  wrote:
> 
>> 
>> On May 27, 2021, at 3:35 AM, Taylor R Campbell 
>>  wrote:
>> 
>>> Date: Wed, 26 May 2021 19:46:57 -0700
>>> From: Jason Thorpe 
>>> 
>>> The test program sets up a SIGFPE handler, and the handler make a
>>> copy of the siginfo, sets a global flag, and returns.  The program
>>> then does "1.0 / 0.0" and prints the result.  It checks to ensure
>>> that the DZE exception is set via fpgetsticky().  It then does "1.0
>>> / 0.0" again, and then verifies that the SIGFPE handler was not
>>> called a second time (because I never cleared DZE with
>>> fpsetsticky()).
>> 
>> This strikes me as wrong.
>> 
>> The status flags (fpgetsticky, fetestexcept) don't determine whether a
>> floating-point operation `signals an exception' (in the language of
>> IEEE 754-2019); they only record whether it happened in the past.
>> 
>> It seems to me that if an operation [ieee754-]signals an exception
>> that the user has asked (with fpsetmask/feenableexcept) to be trapped,
>> then it should deliver a [unix-]signal, irrespective of whether some
>> past operation already [ieee754-]signalled an exception.
> 
> I agree.  I was describing behavior the alpha port already had.  I will write 
> a unit test for this behavior (specifically round DZE), or make sure that 
> there is one that covers it already (there may be … I’m still peeling the 
> onion on the alpha port…)

Ok, circling back on this point, the behavior I described for the alpha port is 
also how amd64 behaves, which is to say “if the an exception is enabled and the 
exception’s flag is already set, then the signal handler will not be called on 
a second triggering of the exceptional condition”.

Consider this test program:

#include 
#include 
#include 
#include 

volatile float f_zero = 0.0;

siginfo_t siginfo_copy;
jmp_buf sigfpe_env;

volatile float f_result;

static void 
sigfpe_action(int sig, siginfo_t *info, void *ctx)
{
siginfo_copy = *info;
longjmp(sigfpe_env, 1);
}   

static void __noinline
divide_by_zero(void)
{   
f_result = 1.0 / f_zero; 
}   

int
main(int argc, char *argv[])
{
struct sigaction sigact = {
.sa_sigaction = sigfpe_action,
.sa_flags = SA_SIGINFO,
};
(void) sigaction(SIGFPE, , NULL);

fp_except_t mask = fpgetmask();

printf("default MASK:\n");
if (mask & FP_X_INV)
printf("\tFP_X_INV\n");
if (mask & FP_X_DZ)
printf("\tFP_X_DZ\n");
if (mask & FP_X_OFL)
printf("\tFP_X_OFL\n");
if (mask & FP_X_UFL)
printf("\tFP_X_UFL\n");
if (mask & FP_X_IMP)
printf("\tFP_X_IMP\n");
#ifdef FP_X_IOV
if (mask & FP_X_IOV)
printf("\tFP_X_IOV\n");
#endif
 
fpsetmask(FP_X_DZ);  

if (setjmp(sigfpe_env)) {
printf("SIGFPE 1 received: signo=%d code=%d\n",
siginfo_copy.si_signo,
siginfo_copy.si_code);
} else {
divide_by_zero();
printf("1: 1.0 / f_zero -> %f\n", f_result);
}
   
fp_except_t sticky = fpgetsticky();

if (sticky & FP_X_DZ) {
printf(“1: GOT FP_X_DZ!\n");
}

if (setjmp(sigfpe_env)) {
printf("SIGFPE 2 received: signo=%d code=%d\n",
siginfo_copy.si_signo,
siginfo_copy.si_code);
} else {
divide_by_zero();
printf("2: 1.0 / f_zero -> %f\n", f_result);
}

sticky = fpgetsticky();

if (sticky & FP_X_DZ) {
printf("2: GOT FP_X_DZ!\n");
}

return 0;
}

Running this program on amd64 results in:

the-ripe-vessel:thorpej 50$ ./fptest 
default MASK:
SIGFPE 1 received: signo=8 code=3
2: 1.0 / f_zero -> inf
2: GOT FP_X_DZ!
the-ripe-vessel:thorpej 51$ 

…which seems very counter-intuitive to me.

I enable FP_X_DZ, I get the SIGFPE signal the first time, and fpgetsticky() 
does NOT indicate FP_X_DZ … yet I do not get the second SIGFPE, and I get 
FP_X_DZ from the second call to fpgetsticky()?

WTF is going on here?

-- thorpej



Re: Expected behavior when returning from a SIGFPE handler

2021-05-27 Thread Jason Thorpe



> On May 27, 2021, at 6:29 AM, Jason Thorpe  wrote:
> 
> 
>> On May 27, 2021, at 3:35 AM, Taylor R Campbell 
>>  wrote:
>> 
>> The default exception handling defined in IEEE 754-2019 precisely
>> defines what the results of the operation should be, so there's no
>> semantic ambiguity about what the program should observe when it
>> proceeds on return from the signal handler.
> 
> Hm, I guess this means that I really need to do software-completion for DZE 
> faults, as well, even if SWC was not explicitly requested for it (we already 
> do that for INV faults to handle QNaNs).  Otherwise, I’m going to be stuck 
> with UNPREDICTABLE as the result, and that’s not great.

…and while we’re at it…

The fenv(3) man suggests that if you have a SIGFPE handler set up in your 
program with exceptions enabled, and you do a:

feholdexcept();
/* do stuff that might raise an exception. */
feupdateenv();

…that the feupdateenv() call would result in the SIGFPE being delivered to the 
handler.

Not sure if we have unit tests for that stuff at the moment, but I’m pretty 
sure the alpha will not do that correctly as it stands (the fenv implementation 
for alpha appears to have been lifted from a FreeBSD implementation, and it 
interacts with the FPCR directly, rather than the architecturally mandated FP_C 
virtual register).

-- thorpej



Re: Expected behavior when returning from a SIGFPE handler

2021-05-27 Thread Mouse
>> In some cases, you want to re-execute the instruction.  A simple
>> example is "FPU disabled" on architectures that have such a notion,
>> eg for lazy FPU switching.
> Weâ??re not talking about lazy FPU switching hereâ?? weâ??re talking about p$

(Or timing.  Yes.)  A better example might be turning on
denormalized-result exceptions (does 754 specify them? I'm fairly sure
I've seen at least one FPU that's documented to support them) with a
handler that handles them by setting a global flag, disabling the
exception, and re-executing the instruction, so as to note that a
denormal occurred but otherwise proceeding with the computation.

>> On others, like the VAX, [advancing over an instruction is] a right
>> pain to do in software.
> Iâ??m not particularly concerned about VAX in this case; it doesnâ??t
> have IEEE 754 floating point, so all bets are off :-)

68k, then, or x86 - anything with 754 FP and CISCy enough to have
variable-sized instructions.  The VAX just happens to be the CISC
architecture I know best.

/~\ The ASCII Mouse
\ / Ribbon Campaign
 X  Against HTMLmo...@rodents-montreal.org
/ \ Email!   7D C8 61 52 5D E7 2D 39  4E F1 31 3E E8 B3 27 4B


Re: Expected behavior when returning from a SIGFPE handler

2021-05-27 Thread Jason Thorpe


> On May 27, 2021, at 3:35 AM, Taylor R Campbell 
>  wrote:
> 
> The default exception handling defined in IEEE 754-2019 precisely
> defines what the results of the operation should be, so there's no
> semantic ambiguity about what the program should observe when it
> proceeds on return from the signal handler.

Hm, I guess this means that I really need to do software-completion for DZE 
faults, as well, even if SWC was not explicitly requested for it (we already do 
that for INV faults to handle QNaNs).  Otherwise, I’m going to be stuck with 
UNPREDICTABLE as the result, and that’s not great.

-- thorpej



Re: Expected behavior when returning from a SIGFPE handler

2021-05-27 Thread Jason Thorpe



> On May 26, 2021, at 8:14 PM, Mouse  wrote:
> 
>> But the x86_64 code appears to return to the same instruction, banging its h$
> 
>> It's my belief that the alpha behavior is more desirable.
> 
>> Please, discuss.
> 
> I could argue that either way.
> 
> In some cases, you want to re-execute the instruction.  A simple
> example is "FPU disabled" on architectures that have such a notion, eg
> for lazy FPU switching.

We’re not talking about lazy FPU switching here… we’re talking about performing 
an FP operation that causes an exception per the IEEE 754 rules. The traps 
generated in these two situations are easily distinguishable, and user-space 
code has no visibility into lazy FPU switching traps (modulo the sorts of 
speculative execution information leaks that modern CPUs are vulnerable to).

This is all about what to do AFTER the user-specified signal handler has been 
called, and it returns to normal program flow via the sigreturn path.

> In some cases, you don't want to.  An example might be soft-float, or
> partial soft-float (such as, emulation of cases the hardware doesn't
> handle).

As I mentioned, on the Alpha, the PALcode pushes a trap frame with the PC 
pointing at least one instruction *after* the one that triggered the arithmetic 
exception (on EV6, it’s the instruction immediately following, on pre-EV6, you 
have to go looking backwards in the instruction stream for it).

> On architectures like SPARC or, I think - you'd know better than I -
> Alpha, where there are very few possible instruction sizes, sometimes
> as few as just one, advancing past the instruction in the trap handler
> is easy even if you have to do it in software.  On others, like the
> VAX, it's a right pain to do in software.  On the latter sort, ideally,
> the hardware would give you both the PC to use to re-execute the
> instruction and the PC to use to skip the instruction, letting the trap
> handler choose, but I'm not aware of any architecture that does that.
> (The closest I'm aware of is, ironically, the SPARC, on which advancing
> past an instruction in software is about as simple as it gets - but it
> has both PC and next-PC in hardware, though admittedly for other
> reasons.)
> 
> I see no clear single right answer here.

I’m not particularly concerned about VAX in this case; it doesn’t have IEEE 754 
floating point, so all bets are off :-)

-- thorpej



Re: Expected behavior when returning from a SIGFPE handler

2021-05-27 Thread Jason Thorpe


> On May 27, 2021, at 3:35 AM, Taylor R Campbell 
>  wrote:
> 
>> Date: Wed, 26 May 2021 19:46:57 -0700
>> From: Jason Thorpe 
>> 
>> The test program sets up a SIGFPE handler, and the handler make a
>> copy of the siginfo, sets a global flag, and returns.  The program
>> then does "1.0 / 0.0" and prints the result.  It checks to ensure
>> that the DZE exception is set via fpgetsticky().  It then does "1.0
>> / 0.0" again, and then verifies that the SIGFPE handler was not
>> called a second time (because I never cleared DZE with
>> fpsetsticky()).
> 
> This strikes me as wrong.
> 
> The status flags (fpgetsticky, fetestexcept) don't determine whether a
> floating-point operation `signals an exception' (in the language of
> IEEE 754-2019); they only record whether it happened in the past.
> 
> It seems to me that if an operation [ieee754-]signals an exception
> that the user has asked (with fpsetmask/feenableexcept) to be trapped,
> then it should deliver a [unix-]signal, irrespective of whether some
> past operation already [ieee754-]signalled an exception.

I agree.  I was describing behavior the alpha port already had.  I will write a 
unit test for this behavior (specifically round DZE), or make sure that there 
is one that covers it already (there may be … I’m still peeling the onion on 
the alpha port…)

>> The alpha code has, for a very long time, always advanced the PC
>> past the faulting instruction on an arithmetic trap[1].  This, in
>> essence, makes it behave exactly as if the exception were disabled,
>> while still giving the handler a chance to "do something").
>> 
>> But the x86_64 code appears to return to the same instruction,
>> banging its head against the proverbial wall.
>> 
>> It's my belief that the alpha behavior is more desirable.
> 
> I agree.  It would be perfectly reasonable to use a SIGFPE handler to,
> say, record a history of the instructions (and perhaps stack traces)
> that signalled floating-point exceptions, to give more precise
> diagnostic information about where they're happening than the status
> flags do -- without otherwise interrupting the flow of the program.
> 
> The default exception handling defined in IEEE 754-2019 precisely
> defines what the results of the operation should be, so there's no
> semantic ambiguity about what the program should observe when it
> proceeds on return from the signal handler.

Ok, I will write a unit test that verifies this behavior.

Thanks!

-- thorpej



Re: Expected behavior when returning from a SIGFPE handler

2021-05-27 Thread Taylor R Campbell
> Date: Wed, 26 May 2021 19:46:57 -0700
> From: Jason Thorpe 
> 
> The test program sets up a SIGFPE handler, and the handler make a
> copy of the siginfo, sets a global flag, and returns.  The program
> then does "1.0 / 0.0" and prints the result.  It checks to ensure
> that the DZE exception is set via fpgetsticky().  It then does "1.0
> / 0.0" again, and then verifies that the SIGFPE handler was not
> called a second time (because I never cleared DZE with
> fpsetsticky()).

This strikes me as wrong.

The status flags (fpgetsticky, fetestexcept) don't determine whether a
floating-point operation `signals an exception' (in the language of
IEEE 754-2019); they only record whether it happened in the past.

It seems to me that if an operation [ieee754-]signals an exception
that the user has asked (with fpsetmask/feenableexcept) to be trapped,
then it should deliver a [unix-]signal, irrespective of whether some
past operation already [ieee754-]signalled an exception.

> The alpha code has, for a very long time, always advanced the PC
> past the faulting instruction on an arithmetic trap[1].  This, in
> essence, makes it behave exactly as if the exception were disabled,
> while still giving the handler a chance to "do something").
> 
> But the x86_64 code appears to return to the same instruction,
> banging its head against the proverbial wall.
> 
> It's my belief that the alpha behavior is more desirable.

I agree.  It would be perfectly reasonable to use a SIGFPE handler to,
say, record a history of the instructions (and perhaps stack traces)
that signalled floating-point exceptions, to give more precise
diagnostic information about where they're happening than the status
flags do -- without otherwise interrupting the flow of the program.

The default exception handling defined in IEEE 754-2019 precisely
defines what the results of the operation should be, so there's no
semantic ambiguity about what the program should observe when it
proceeds on return from the signal handler.


Re: Expected behavior when returning from a SIGFPE handler

2021-05-26 Thread Mouse
> But the x86_64 code appears to return to the same instruction, banging its h$

> It's my belief that the alpha behavior is more desirable.

> Please, discuss.

I could argue that either way.

In some cases, you want to re-execute the instruction.  A simple
example is "FPU disabled" on architectures that have such a notion, eg
for lazy FPU switching.

In some cases, you don't want to.  An example might be soft-float, or
partial soft-float (such as, emulation of cases the hardware doesn't
handle).

On architectures like SPARC or, I think - you'd know better than I -
Alpha, where there are very few possible instruction sizes, sometimes
as few as just one, advancing past the instruction in the trap handler
is easy even if you have to do it in software.  On others, like the
VAX, it's a right pain to do in software.  On the latter sort, ideally,
the hardware would give you both the PC to use to re-execute the
instruction and the PC to use to skip the instruction, letting the trap
handler choose, but I'm not aware of any architecture that does that.
(The closest I'm aware of is, ironically, the SPARC, on which advancing
past an instruction in software is about as simple as it gets - but it
has both PC and next-PC in hardware, though admittedly for other
reasons.)

I see no clear single right answer here.

/~\ The ASCII Mouse
\ / Ribbon Campaign
 X  Against HTMLmo...@rodents-montreal.org
/ \ Email!   7D C8 61 52 5D E7 2D 39  4E F1 31 3E E8 B3 27 4B


Re: Expected behavior when returning from a SIGFPE handler

2021-05-26 Thread Paul Goyette

On Wed, 26 May 2021, Jason Thorpe wrote:


...
The alpha code has, for a very long time, always advanced the PC past
the faulting instruction on an arithmetic trap[1].  This, in essence,
makes it behave exactly as if the exception were disabled, while still
giving the handler a chance to "do something").

But the x86_64 code appears to return to the same instruction,
banging its head against the proverbial wall.

It's my belief that the alpha behavior is more desirable.


I agree.  To me, the x86_64 behaviour as reported sounds quite "sub-
optimal" and rather unuseful.


++--+---+
| Paul Goyette   | PGP Key fingerprint: | E-mail addresses: |
| (Retired)  | FA29 0E3B 35AF E8AE 6651 | p...@whooppee.com |
| Software Developer | 0786 F758 55DE 53BA 7731 | pgoye...@netbsd.org   |
++--+---+


Expected behavior when returning from a SIGFPE handler

2021-05-26 Thread Jason Thorpe
I've been working on fixing some test case failures on the Alpha port, and I'm 
elbow-deep in FP-land right now.  The Alpha has a somewhat complicated FP story 
because it has architecture-mandated software completion for essentially 
anything outside the happy path in hardware, and to support that it has a 
virtual floating point control register called "FP_C" that is completely 
orthogonal to the hardware floating point control register (called "FPCR").  
(Don't get me started on the "trap shadow"...)

I wrote a reduced test case program to exercise the different code paths around 
the DZE exception.  I can run this program in 2 different modes: DZE traps 
disabled (the default), DZE traps enabled.

The test program sets up a SIGFPE handler, and the handler make a copy of the 
siginfo, sets a global flag, and returns.  The program then does "1.0 / 0.0" 
and prints the result.  It checks to ensure that the DZE exception is set via 
fpgetsticky().  It then does "1.0 / 0.0" again, and then verifies that the 
SIGFPE handler was not called a second time (because I never cleared DZE with 
fpsetsticky()).

In the "disabled" case, the test program performs the same way on x86_64 and 
alpha (modulo the result of dividing by zero... on x86_64 I consistently get 
+inf, and on alpha sometimes I get +inf, sometimes I get 0; the AARM officially 
states that the results are UNPREDICTABLE).

In the "enabled" case, the alpha and x86_64 behavior differ!  On the alpha, the 
program progresses all the way to the end.  But on x86_64, it hangs... spinning 
around the SIGFPE handler.

The alpha code has, for a very long time, always advanced the PC past the 
faulting instruction on an arithmetic trap[1].  This, in essence, makes it 
behave exactly as if the exception were disabled, while still giving the 
handler a chance to "do something").

But the x86_64 code appears to return to the same instruction, banging its head 
against the proverbial wall.

It's my belief that the alpha behavior is more desirable.

Please, discuss.

[1] The PALcode, in fact, does this when delivering the exception.  The alpha 
FP trap handler has to do some shenanigans to find the actual faulting PC.  
These shenanigans are simple (PC += 4) for EV6 and later CPUs, and somewhat 
complicated for pre-EV6 (which has to deal with the "trap shadow").  In any 
case, even when software completion is not possible because trap shadow rules 
were violated by either the program-writer or the compiler, you're pretty much 
guaranteed to not execute the faulting instruction again.

-- thorpej