The friendship between Haskell and C
typeclasses.substack.com
typeclasses.substack.com
Calling Rust functions from Haskell FFI:
https://engineering.iog.io/2023-01-26-hs-bindgen-introductio...
Oh, note to self: signal handlers need to restore errno values if they call async-signal-safe functions that can set errno.
http://www.cs.ox.ac.uk/people/jeremy.gibbons/publications/fa...
sin(±infinity) returns a NaN and raises the "invalid"
floating-point exception.
However, I can't figure out how to make that happen, and my clumsy attempts to figure out <fenv.h> and feraiseexcept() failed to make sin(INFINITY) do something other than return a NaN.(edit), for instance this gives me output of "1":
#include <math.h>
#include <fenv.h>
#include <stdio.h>
int main(void)
{
double a = sin(INFINITY);
printf("%d\n", fetestexcept(FE_INVALID));
return 0;
}I think I have an outdated idea that there should be a way to have the exception trigger a SIGFPE, and that modern processors don't do that.
And testing just now, I can't even trigger SIGFPE with division by zero (integer or float). I guess all of these exceptions are "caught" now.
a.out: Mach-O 64-bit executable arm64
while my gcc-12 compiles to: a.out: Mach-O 64-bit executable x86_64
which is then emulated via Rosetta.Although, in that case, this function would still be pure as far as Haskell is concerned. Because its output only depends on its input. Interrupts/exceptions are outside of the scope of the type system as far as an individual function goes.
If you care about it, you need to use the appropriate flags to get a strict floating-point model. If you merely have a precise or fast floating-point model, calling fetestexception is going to result in unspecified behavior.
[If you really want to be strict, the result of flags are unspecified if you don't use #pragma FENV_ACCESS ON, but gcc refuses to support FENV_ACCESS pragma, and you instead need to use command-line flags to get the same effect.]
> I think I have an outdated idea that there should be a way to have the exception trigger a SIGFPE, and that modern processors don't do that.
On Intel, you can clear the exception mask for the INVALID FP exception in mxcsr and get an SIGFPE whenever an operation raises INVALID [assuming, again, strict model to ensure the optimizer respects fp exceptions.] I don't know how, or if, AArch64 can enable hardware traps on floating-point exceptions.
Some architectures (but not all) do include some bits in the floating-point control register that will cause a hardware trap to be generated on an exception. There's no standard C function to do this, and even compiler builtin support is kind of sketchy, so your best bet is usually resorting to inline assembly to do this.
The strict model requires that everything be evaluated at runtime and that the optimizer be aware of the potential for floating-point operations to have interactions with floating-point control and status words (i.e., rounding modes and fp exceptions)--essentially all optimizations are floating-point are forbidden.
The precise model allows compilers to ignore the dependency on rounding mode and fp exceptions, and treat floating-point optimizations as side-effect-free pure expressions, but still requires them to generally follow IEEE 754 arithmetic. As such, standard optimizations like code motion or constant propagation are legal, but arithmetic optimizations (like replacing x + 0.0 with x) are not generally legal.
The fast model allows compilers to also apply the arithmetic optimizations.
Most people who actually care about this level of detail are aware of the panoply of compiler options that are used to control floating-point optimization in the compiler.
Edit: Hee-hee! And if I put my source into Godbolt's Compiler Explorer, compile with gcc 12.2 -O3 -ffast-math then my program turns into:
main:
xor eax, eax
ret(Speaking of fast math, my planned blog post title if I ever write all this up into a blog post is "Floating Point, or How I Learned to Start Worrying and Hate Fast Math.")
More precisely, my test compared the bit-patterns directly:
i = 0 .. 2^32-1
memcpy(&x, &i, 4);
y = x + 0.0f;
memcpy(&j, &y, 4)
if (i != j) {printf("%f + 0.0 -> %f\n", x, y);
and I'm pretty sure that NaN addition, while resulting in a NaN, does not need to preserve the original bit pattern.Regarding fast-not-so-math - Praise Kahan!
"it be complicated"
Except for certain operations (fneg, fcopysign, and fabs are all defined to only affect the sign bit, to the point that they're an exception to the sNaN-always-signals rule; and there are a few operations defined to operate on the NaN payload information), IEEE 754 generally says no more than a qNaN is generated. There's a recommendation that NaN payloads propagate, and most hardware does do that, but compilers actually typically don't even guarantee that sNaNs are not produced by operations.
I looked more closely, and yes, the signalling NaN + 0.0 always changed the bit pattern, and the quiet NaN + 0.0 always stayed the same.
Thank you for your informative details about IEEE 754.
... can it? I don't see it in the man page. The rest is a good point, either way, though.
Haskell is not about removing side effects in the first place-- it's about making them obvious and contained, and about declaring them. It's about the type system imposing certain constraints on what's allowed and where: none of the above really applies to C.
One way to think of Haskell is like a pure functional C preprocessor that also happens to be lazy. When Haskell code is running, pure functional expressions are being evaluated and reduced to their simplest form. This simplest form is not a plain value but actually a program that when run has effects (reading input, printing to the screen) just like a C program.
The last piece of the puzzle is that, unlike the C preprocessor, Haskell doesn’t do all of the evaluation ahead of time. The evaluation of these pure functional expressions is interwoven with the execution of the program producing the desired effects. Haskell’s type system is used to express the dependencies between inputs and outputs so that the effects are performed in the correct sequence.
"The C language is purely functional" and "Haskell is the world's finest imperative programming language" can blow some minds.
There's also:
> “Haskell is a dynamically-typed, interpreted language.”
From https://aphyr.com/posts/342-typing-the-technical-interview
You can notice on the GP's description that Haskell will generate those programs at runtime. The Haskell code is running on the same level as the C compiler, but at a very different time.
The actually interesting part of Haskell is that the type system is strong enough to indicate which sections of the code is pure and which are unpure.
The expression language is absolutely pure.