12

Could you please help me to understand what is going on with FPU Control Word in my Delphi application, on Win32 platform.

When we create a new VCL application, the control word is set up to 1372h. This is the first thing I don't understand, why it is 1372h instead of 1332h which is the Default8087CW defined in System unit.

The difference between these two:

1001101110010  //1372h
1001100110010  //1332h

is the 6th bit which according to documentation is reserved or not used.

The second question regards CreateOleObject.

function CreateOleObject(const ClassName: string): IDispatch;
var
  ClassID: TCLSID;
begin
  try
    ClassID := ProgIDToClassID(ClassName);
{$IFDEF CPUX86}
    try
      Set8087CW( Default8087CW or $08);
{$ENDIF CPUX86}
      OleCheck(CoCreateInstance(ClassID, nil, CLSCTX_INPROC_SERVER or
        CLSCTX_LOCAL_SERVER, IDispatch, Result));
{$IFDEF CPUX86}
    finally
      Reset8087CW;
    end;
{$ENDIF CPUX86}
  except
    on E: EOleSysError do
      raise EOleSysError.Create(Format('%s, ProgID: "%s"',[E.Message, ClassName]),E.ErrorCode,0) { Do not localize }
  end;    
end;

The above function is changing control word to 137Ah, so it is turning on the 3rd bit (Overflow Mask). I don't understand why it is calling Reset8087CW after, instead of restoring the state of the word which was before entering into the function?

Wodzu
  • 6,932
  • 10
  • 65
  • 105

2 Answers2

7

The 6th bit is reserved and ignored. Those two control words are in fact equal in the sense that the FPU behaves the same. The system just happens to set the reserved bit. Even if you attempt to set the value to $1332, the system will set it to $1372. No matter what value you ask the 6th bit to have, it will always be set. So, when comparing these values you have to ignore that bit. Nothing to worry about here.

As for CreateOleObject the authors decided that if you are going to use that function then you are also going to mask overflow when using the COM object, and indeed beyond. Who knows why they did so, and for 32 bit code only? Probably they found a bunch of COM objects that routinely overflowed, and so added this sticking plaster. It wasn't enough to mask overflow on creation, it also need to be done when using the object so The RTL designers chose to unmask overflow henceforth.

Or perhaps it was a bug. They decided not to fix it for 32 bit code because people relied on the behaviour, but they did fix for 64 bit code.

In any case this function does nothing very special. You don't need to use it. You can write your own that does what you want it to do.

Floating point control is a problem when working with interop. Delphi code expects unmasked exceptions. Code built with other tools typically masks them. Ideally you would mask exceptions when you call out of your Delphi code and unmask them on return. Expect other libraries to arbitrarily change the control word. Also be aware that Set8087CW is not thread safe which is a massive problem that Embarcadero have refused to address for many years.

There's no easy way forward. If you aren't using floating point in your program then you could simply mask exceptions and probably be fine. Otherwise you need to make sure that the control word is set appropriately at all points in all threads. In general that is close to impossible using the standard Delphi RTL. I personally handle this by replacing the key parts of the RTL with threadsafe versions. I have documented how to do so in this QC report: QC#107411.

David Heffernan
  • 601,492
  • 42
  • 1,072
  • 1,490
  • Downloaded the file in your QC report. Note that the file name is chopped off and without extension. Adding `.zip` fixed that problem. Pity that @AllenBauer could not fix that long standing RTL bug while he was onboard. – LU RD Sep 25 '16 at 08:24
  • 2
    @LURD I think he wanted to but management would not allow it. – David Heffernan Sep 25 '16 at 08:25
  • Thanks, that is what I've thought. It did not come to my mind that there is no possibility to set CW to $1332 tho. – Wodzu Sep 25 '16 at 16:51
  • Note that Delphi RTL uses floating point in all sorts of places, including memory management (e.g. some variants of `Move()`) so it's pretty impossible to guarantee that you are not using floating point somewhere. – Marc Durdin Nov 09 '20 at 04:48
  • @Marc In that example it is ot a problem, as all that is done is using registers and not performing arithmetic. – David Heffernan Nov 09 '20 at 07:29
  • @David, it can be an indirect issue if there is mess on the FPU stack from earlier code, e.g.: https://stackoverflow.com/a/11365823/1836776 – Marc Durdin Nov 09 '20 at 21:45
  • @David, by the way, that QC report on your workaround is no longer available. It sounds very helpful. Do you have the details elsewhere? – Marc Durdin Nov 09 '20 at 21:47
  • @DavidHeffernan Same question as above. – Thijs van Dien Jan 04 '21 at 00:14
0

Disclaimer: I debugged the questions in Delphi XE.

First, the second question.

If you look at the code of Set8087CW you will see that it stores the new FPU CW value in Default8087CW variable, and Reset8087CW restores FPU CW from Default8087CW; so the Reset8087CW call after Set8087CW does nothing at all, which is demonstrated by

Memo1.Lines.Clear;
Memo1.Lines.Add(IntToHex(Get8087CW, 4));   // 1372
Set8087CW( Default8087CW or $08);
Memo1.Lines.Add(IntToHex(Get8087CW, 4));   // 137A
Reset8087CW;
Memo1.Lines.Add(IntToHex(Get8087CW, 4));   // 137A

Evidently a bug.

Now the first question - it was interesting debugging exercise.

The Default8087CW value of Delphi VCL application is changed from hex 1332 to 1372 by Windows.CreateWindowEx function, called from Classes.AllocateHWnd, called from TApplication.Create, called from initialization section of Controls.pas unit.

Have a look at CreateWindowEx code - it explains what happens. I don't really want to discuss it further - the FPU support in Delphi is too messy and buggy.

David Heffernan
  • 601,492
  • 42
  • 1,072
  • 1,490
kludg
  • 27,213
  • 5
  • 67
  • 118
  • `Reset8087CW` does indeed do something. It sets the control word to be whatever is currently to be found in `Default8087CW`. And that could very well have an effect if `CoCreateInstance` has modified the control word, as often happens. And your other analysis is wrong. `CreateWindowEx` does call `Set8087CW` but it passes the value returned by a call to `Get8087CW`. Good luck getting the FP unit to hold `$1332`. It just won't let you do that. – David Heffernan Sep 25 '16 at 10:48
  • So, if you debug this more carefully you will see that the FPU is initialised in a call to `_FpuInit` made from the initialization section of `System`. It passes whatever is in `Default8087CW`, typically `$1332`, but the system forces the 6th bit to be set and so the value set is `$1372`. Nothing whatsoever to do with any calls to `CreateWindowEx`. – David Heffernan Sep 25 '16 at 10:56
  • @DavidHeffernan - the analysis is correct, I've traced how `Default8087CW` changes in debugger. FPU CW never set to `$1332`, and `CreateWindowEx` sets `Default8087CW` to `$1372` by taking `$1372` from reading FPU CW. – kludg Sep 25 '16 at 10:58
  • The analysis is completely incorrect. Try again. It is exactly as I explain. Starts at Windows default of `$027F` and then is set to `$1332` in `System` unit call to `_FpuInit`, but the system forces bit 6 and so the value is `$1372`. Set a breakpoint on that call to `_FpuInit` from `System` initialization code and see that I am right. – David Heffernan Sep 25 '16 at 11:01
  • @DavidHeffernan - well I've said I use Delphi XE, and the analysis is completely correct. – kludg Sep 25 '16 at 11:02
  • Nope, it's been this way since very early Delphi versions, way before XE. The fact that `Default8087CW` changes is irrelevant. That's not the value in the FPU itself. I think that's where you have got confused. That value does indeed change when `CreateWindowEx` passes the result of `Get8087CW` to `Set8087CW`. But that's because the control word is already `$1372` because it is **impossible** for it ever to be `$1332`. – David Heffernan Sep 25 '16 at 11:03
  • @DavidHeffernan you are wrong. `_FpuInit` called from `System.pas` initialization section does not change `Default8087CW` value. Relax and debug yourself. – kludg Sep 25 '16 at 11:07
  • The question is not about the value of `Default8087CW`, rather the value of the control word. Try a console app. You'll find that `Default8087CW` is `$1332` but the control word is `$1372`. You are correct in your analysis of what happens to `Default8087CW`. It's what happens to the control word itself that matters. Use the FPU window to inspect that. – David Heffernan Sep 25 '16 at 11:09
  • The question is about VCL app, not console app. – kludg Sep 25 '16 at 11:10
  • Try running this program to see what I mean: `uses SysUtils; begin Writeln(IntToHex(Get8087CW, 4)); Writeln(IntToHex(Default8087CW, 4)); Readln; end. ` – David Heffernan Sep 25 '16 at 11:10
  • Regarding console apps, the point I am making is that the control word can never be `$1332`. It is **impossible**. You don't need that call to `CreateWindowEx` to be made for the control word to be `$1372`. It's just that call that sets `Default8087CW` to be `$1372`. Since `$1332` means the same as `$1372` it doesn't actually matter. Bit 6 is ignored. To prove that, try this: `uses SysUtils; begin Set8087CW($1332); Writeln(IntToHex(Get8087CW, 4)); Readln; end. ` – David Heffernan Sep 25 '16 at 11:13
  • I know what you mean, but you don't want to know what I mean. FPU CW is never `1332`, but `Default8087CW` **is** `1332` when application starts, and it remains `1332` in console app, but not in VCL app. – kludg Sep 25 '16 at 11:15
  • I do know what you mean. I agree with that, and I tried to say so in an earlier comment. Your analysis of `Default8087CW` behaviour is correct, but not really the point. It's just that I feel you've missed the point and that the question is about the control word rather than the global variable. In any case it's all completely meaningless since this bit 6 is ignored. – David Heffernan Sep 25 '16 at 11:17
  • All you need to do is recognise that the question is about the control word and not the variable, and speak to that in your answer. I agree I was slow to pick up on what you were saying but then I was thinking about the control word which is surely the point and surely what actually matters. – David Heffernan Sep 25 '16 at 11:19