After running a few tests, this appears only in -Xint mode on Windows/x86.
It doesn't appear with -client -Xcomp or -server -Xcomp on Windows, or on
Solaris/x86 in -Xint mode.
It also doesn't appear to be a VM bug. Dropping the 1.4.1 VM into a 1.4.2 JDK
exhibits the same problem. Dropping a 1.4.2 VM into a 1.4.1 JDK does not.
I recall seeing a recent putback to the AWT or 2D code which mucked with the
FPU control word, but seem to recall this code was only being executed on
some consumer versions of Windows (e.g. 98) and not on NT/2000, which is the
platform tested. Adding some relevant engineers to the interest list.
Note that in C1 we only touch the FPU control word for methods that do long
sequences of single-precision floating point, and I'm pretty sure the same
holds true for C2. This test does only double-precision floating-point.
Running with -Xcomp -XX:CompileOnly=Zjb fails in the same way -Xint does.
This strongly indicates that the reason it works for both C1 and C2 in -Xcomp
mode is that we're compiling and running some other method which causes the
FPU control word to be reset.
The attached boiled-down test case clearly indicates that the control word
change is occurring in native code called by the constructor for java.awt.Frame.
Interestingly, if one uses a javax.swing.JFrame for this purpose the bug doesn't
occur. Run the attached Zjb5.java with no command line arguments to reproduce
the bug, and with "-swing" to observe that the bug doesn't occur.
A stopgap measure would be to put in SAVE_CONTROLWORD and RESTORE_CONTROLWORD
in the native methods invoked by the Frame constructor. Better would be to
understand the new (since 1.4.1) API calls being used that are causing this
problem and bracket only those API calls.
I think the bug Ken is referring to is 4351747 which was fixed by 2D in Mantis.
The affected code path is clearly the native code for the constructor of
java.awt.Frame. Please see the above evaluation and the test case.
As noted by Ken this is nothing to do with 4351747 which was implemented
as suggested by the JDK's floating point engineer, and in any case doesn't
even get executed unless you run on Windows 95 AND you print. Neither applies
to this test case.
This is in fact a d3d-related problem; reassigning to 2D...
The problem appears to be d3d's desire to set the FPU into single-precision
mode for the duration of the process. This happens whenever we create a d3d
device. There is a way to disable this (by forcing d3d to go into and out
of single-precision mode with every d3d call) by using the DDSCL_FPUPRESERVE
flag when we set the cooperative level for ddraw. (We are obviously not
doing this now, thus the bug).
You can work around the problem now using the d3d-disabling flags documented
in the WorkAround field. The fix is as simple as adding this FPU flag to
SetCooperativeLevel(), but we need to test with this flag to make sure that
it has no serious performance impact.
By the way, this bug should also be present in 1.4.1_02, since that code
is fairly similar to the current 1.4.2 code, with respect to the d3d code.
The bug may also be present in 1.4.1 and 1.4.1_01, although it looks like
(from the DirectX docs) this may be specific to the DX7 d3d interface, which
we only started using in 1.4.1_02 and 1.4.2.
I've regressed the bug and found:
- The bug occurs in 1.4.1_02, but not 1.4.1 or 1.4.1_01. This must mean that
the bug is related to the new use of the DX7 interfaces as of 1.4.2_02.
- The bug started occuring in 1.4.2 as of build 7 (which was the build that
integrated our 1.4.1_02 changes into 1.4.2).
- The -Dsun.java2d.d3d=false workaround works universally, regardless of
the release. The environment variable workaround (J2D_D3D=false) has
no effect in 1.4.1_02 and has no effect in 1.4.2 until build 12. This must
be the build where we first introduced that variable, to help with some
new d3d functionality.
The trick now is to validate that this fix is correct and to test for
quality and performance regressions.
More information about the situations that can affect the appearance of this bug:
The D3D docs state that the behavior of having the FPU set to single-precision mode by default in SetCooperativeLevel() is new in DX7. Prior to DX7, the default behavior was that which you now must request by using the new DDSCL_FPUPRESERVE flag in SetCooperativeLevel; you could request that d3d set the FPU into single-precision mode at creation time by using the DDSCL_FPUSETUP flag, but this flag was not enabled by default. Now it is. This maps to what we are seeing, where the bug first surfaced when we started using the DX7 interfaces in 1.4.1_02 and 1.4.2 b7. I also note that disabling our use of DX7 internally makes the bug go away (although this is only a debugging option, not a real fix to the problem obviously).
Also, I traced some of the behavior in Swing vs AWT. Although I did not chase the behavior into the exact call(s) that cause Swing to work, I did notice that the behavior difference comes in JRootPane, when we create the glass pane; if we disable that creation step things break in Swing just as they do in Awt. Also, I can see d3d is, in fact, setting the FPU to single-precision mode in both cases; it's just that something after that fact causes the FPU to be reset to the default double-precision mode. This could be through some call to LoadLibrary, which the D3D docs call out specifically as an event that may reset the FPU to double-precision. It would be interesting to find out exactly what we are doing that causes this reset to happen, although it is somewhat of an academic issue at this point; it does not change the bug or the fix.
Another interesting point from the d3d docs is that the state of the FPU is thread-specific. This means that creating the d3d device on one thread may set the FPU to single-precision mode on that thread, but another thread may still use double-precision. For example, one test app I wrote showed that PI
was incorrectly calculated (using the sample code in the Description field) on the main thread after the creation of the Frame, but that a new thread calculated PI correctly after that. This behavior implies a couple of interesting things:
- Apps that perform calculations in threads other than that used to initialize d3d (usually the main thread or another thread that creates the first
onscreen or offscreen windows or images) will probably not see this bug because the FPU is still in double-precision mode in that other thread.
- Our current approach to multi-threaded rendering (where we allow calls into
DirectX from arbitrary threads) means that d3d may or may not be using
single-precision in its calculations, based on what thread it is called from.
This is allowable behavior (according to the d3d docs); it just means that
d3d will suffer performance penalties from doing double-precision calculations
where single-precision is considered good enough.
note on the fix: we not only have to fix the call to SetCooperativeLevel() in
ddrawObject.cpp's CreateDDrawObject() method, but also in ddrawObject.h's
dx7 implementation of SetCooperativeLevel(). This is because we may call
SetCooperativeLevel() at any time, based on whether we are going into or
out of fullscreen mode.