Home > Mobile >  Is AtomicCmpExchange() broken?
Is AtomicCmpExchange() broken?

Time:11-24

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.

  • Related