If you make a new multi-device application project, set Project > Option > Compiling > Optimization : True
, and then copy the code below to unit1.pas
:
unit Unit1;
interface
uses
System.SysUtils,
FMX.Forms,
FMX.StdCtrls,
System.Classes,
FMX.Types,
FMX.Controls,
FMX.Controls.Presentation;
type
TForm1 = class(TForm)
Button1: TButton;
procedure Button1Click(Sender: TObject);
private
FKey: integer;
public
end;
var
Form1: TForm1;
implementation
{$R *.fmx}
procedure TForm1.Button1Click(Sender: TObject);
begin
FKey := 2;
var LCompareKey: integer := 2;
AtomicCmpExchange(FKey{target}, LCompareKey{NewValue}, LCompareKey{Comparand});
if FKey <> LCompareKey then raise Exception.Create('Error 2');
TThread.queue(nil,
procedure
begin
if LCompareKey <> FKey
then raise Exception.Create('Error 3');
end);
end;
end.
Why does this code crash on Win32 on if FKey <> LCompareKey then raise Exception.Create('Error 2');
?
I'm using Delphi 10.4 Sydney Update 3. I didn't yet try in Delphi 11 Alexandria, so I don't know if it's working in that version.
Is there any workaround except deactivating the optimization?
Another question - is it really safe to activate the optimization?
CodePudding user response:
Yes, codegen for AtomicCmpExchange
is broken on Win32 compiler when optimization is turned on.
Problem happens in combination with anonymous method variable capture that happens in TThread.Queue
call. Without variable capture, assembly code for AtomicCmpExchange
is properly generated.
Workaround for the issue is using TInterlocked.CompareExchange
.
...
var LCompareKey: integer := 2;
TInterlocked.CompareExchange(FKey{target}, LCompareKey{NewValue}, LCompareKey{Comparand});
if FKey <> LCompareKey then raise Exception.Create('Error 2');
...
TInterlocked.CompareExchange
function still uses AtomicCmpExchange
, but at place of call it works with captured variables through parameters instead of directly and generated code is correct in those situations.
class function TInterlocked.CompareExchange(var Target: Integer; Value, Comparand: Integer): Integer;
begin
Result := AtomicCmpExchange(Target, Value, Comparand);
end;
Another, less optimal solution would be turning off optimization around broken method Button1Click
with {$O-}
compiler directive and then turning it back on with {$O }
Since AtomicCmpExchange
is Delphi intrinsic function, its code is directly generated by compiler when it is called and bad codegen only affects that procedure, not general code - in other words anonymous method capture is working correctly in other code (unless there are other, bugs in compiler, unrelated to this particular one).
In other places in RTL where AtomicCmpExchange
is used, there is no code where variable capture is involved, so RTL, VCL and FMX code is not affected by this issue and optimization can be turned on in application.
Note: There may be other optimization bugs in compiler that we don't know about.