Why an application starts with FPU Control Word different than Default8087CW?
Asked Answered
I

2

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?

Impala answered 25/9, 2016 at 6:41 Comment(0)
E
8

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.

Encomiast answered 25/9, 2016 at 7:59 Comment(8)
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.Weathered
@LURD I think he wanted to but management would not allow it.Encomiast
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.Impala
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.Feune
@Marc In that example it is ot a problem, as all that is done is using registers and not performing arithmetic.Encomiast
@David, it can be an indirect issue if there is mess on the FPU stack from earlier code, e.g.: https://mcmap.net/q/1011390/-invalid-float-point-operation-on-moveFeune
@David, by the way, that QC report on your workaround is no longer available. It sounds very helpful. Do you have the details elsewhere?Feune
@DavidHeffernan Same question as above.Kkt
A
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.

Albuminate answered 25/9, 2016 at 9:22 Comment(14)
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.Encomiast
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.Encomiast
@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.Albuminate
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.Encomiast
@DavidHeffernan - well I've said I use Delphi XE, and the analysis is completely correct.Albuminate
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.Encomiast
@DavidHeffernan you are wrong. _FpuInit called from System.pas initialization section does not change Default8087CW value. Relax and debug yourself.Albuminate
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.Encomiast
The question is about VCL app, not console app.Albuminate
Try running this program to see what I mean: uses SysUtils; begin Writeln(IntToHex(Get8087CW, 4)); Writeln(IntToHex(Default8087CW, 4)); Readln; end. Encomiast
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. Encomiast
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.Albuminate
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.Encomiast
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.Encomiast

© 2022 - 2024 — McMap. All rights reserved.