Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Proposal: "strictfp"/"fastfp" modifier #3896

Closed
mburbea opened this issue Jul 10, 2015 · 9 comments
Closed

Proposal: "strictfp"/"fastfp" modifier #3896

mburbea opened this issue Jul 10, 2015 · 9 comments

Comments

@mburbea
Copy link

mburbea commented Jul 10, 2015

I'm not sure which repo this issue belongs in, as it is both a language and compiler feature.
Currently, the CLR makes no guarantee about the result of floating point operations. In cases where applications communicate to each other and need to simulate floating point results locally, the lack of strict mode causes their to be differences in the result of calculation.

I propose a modifier that can be placed on either a class, method or as a standalone block.

public strictfp static class MathExtensions
{
// all calculations in this class will be performed in strict mode.
}
...
public strictfp float DoIt(float x, float y)
{
// the entire method will be strict
}
...
public double ComputeVal()
{
... not strict
     strictfp
    {
      // strict math in this block.
     }
}
...

This should Similar to the method modifier available in Java. When strict mode is enabled, anything computed on a single or double would be truncated to either 32 or 64 bits of precision, and when a fused operation like a multiple and add operation would be called each should be called separately to avoid producing a non-reproducible result.

All of these operations should be done using SSE operators, which I believe should always be reproducible.

As a nice to have, it'd be good if the trig functions were to switch to a strict implementation as well if the current implementation is not.

As part of this proposal, I also suggest adding the opposite modifier "fastfp", when in a block that would be in strict mode, "fastfp" would result in switching back to the normal "fast" behavior.

@HaloFour
Copy link

Just out of curiosity is there a real need for something like this? Note that the modifier was added to Java 1.2 because prior to that it was the default behavior, but it was inherently slower and more error prone, so the default behavior was changed and this modifier added. And my understanding is that the strictfp does not mean that the operations are performed strictly at the specified size, but that the intermediate values are truncated back to the specified size.

@mburbea
Copy link
Author

mburbea commented Jul 13, 2015

I believe so. This should be viewed as the equivalent of compiling a C++ program with a fp:strict flag.

It might be slower, but predictable results are important in many applications. Especially in games where different hardware producing different results can lead to synchronization issues.

Currently, there is a work around in C# but its awful, and requires you to basically write your code like so:

public float SomeComputations(float x, float y)
{
      var s1 = (float)(x-y);
      var s2= (float)(y-x);
      var s3= (float)(s1*s2);
      return (float)s3*s3;
} 

If you throw in a cast at every step, the JIT will truncate at each step. Another alternative is to stick your working variable into an array, which should round trap the operation through memory (which is slow).

However, if you only stick with SSE registers you don't have to worry about the extra precision. Also before someone invokes Eric Lippert, this is an awful answer http://stackoverflow.com/a/14864871. Using integers is hardly an acceptable solution.

@HaloFour
Copy link

@mburbea

So from a perspective of implementation, does the spec guarantee that those casts would be honored by the JIT, or would we need changes to the CLR/JIT such as a pseudo-attribute?

If the casts can be expected to be honored then the implementation sounds relatively simple; the compiler can just emit additional conv.r2 or conv.r4 opcodes after each intermediate operation.

@MrJul
Copy link
Contributor

MrJul commented Jul 13, 2015

The spec does guarantee that the casts are honored. See the following quote (important part in bold at the end).

From ECMA-335, §I.12.1.3 Handling of floating-point data types:

Storage locations for floating-point numbers (statics, array elements, and fields of classes) are of
fixed size. The supported storage sizes are float32 and float64. Everywhere else (on the
evaluation stack, as arguments, as return types, and as local variables) floating-point numbers are
represented using an internal floating-point type.
[...]
When a floating-point value whose internal representation has greater range and/or precision
than its nominal type is put in a storage location, it is automatically coerced to the type of the
storage location. This can involve a loss of precision or the creation of an out-of-range value
(NaN, +infinity, or -infinity). However, the value might be retained in the internal representation
for future use, if it is reloaded from the storage location without having been modified. It is the
responsibility of the compiler to ensure that the retained value is still valid at the time of a
subsequent load, taking into account the effects of aliasing and other execution threads (see
memory model (§I.12.6)). This freedom to carry extra precision is not permitted, however,
following the execution of an explicit conversion (conv.r4 or conv.r8), at which time the
internal representation must be exactly representable in the associated type.

@HaloFour
Copy link

@MrJul

Er, that bold text to me reads that these casts are explicitly honored by the spec.

@MrJul
Copy link
Contributor

MrJul commented Jul 13, 2015

@HaloFour We agree: that's what I said :)

@HaloFour
Copy link

@MrJul

Oops, my reading comprehension ain't so good on a Monday morning.

So it does sound like this feature could be handled purely within the language just by having the compiler spit out a slew of those IL opcodes.

@mburbea
Copy link
Author

mburbea commented Jul 14, 2015

That's true, the compiler can just choose to emit those additional opcodes.

Unfortunately, there is no guarantee about the trigonometric and transcendental functions. Java has a StrictMath class that implements "strict" versions of the algorithms that should result in predictable results. Although that might be best as a coreFx request.

@gafter
Copy link
Member

gafter commented Mar 20, 2017

We are now taking language feature discussion on https://github.com/dotnet/csharplang for C# specific issues, https://github.com/dotnet/vblang for VB-specific features, and https://github.com/dotnet/csharplang for features that affect both languages.

See also #18002 for a general discussion of closing and/or moving language issues out of this repo.

@gafter gafter closed this as completed Mar 20, 2017
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

4 participants