AtomicCmpExchange() 坏了吗?

Is AtomicCmpExchange() broken?

如果新建多机​​应用项目,设置Project > Option > Compiling > Optimization : True,然后复制下面的代码到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.

为什么此代码在 if FKey <> LCompareKey then raise Exception.Create('Error 2'); 上的 Win32 上崩溃?

我正在使用 Delphi 10.4 悉尼更新 3。我还没有尝试 Delphi 11 Alexandria,所以我不知道它是否适用于那个版本。

除了停用优化之外,是否有任何解决方法?

另一个问题-激活优化真的安全吗?

是的,AtomicCmpExchange 的代码生成在打开优化时在 Win32 编译器上被破坏。

问题与 TThread.Queue 调用中发生的匿名方法变量捕获结合使用。没有变量捕获,正确生成 AtomicCmpExchange 的汇编代码。

该问题的解决方法是使用 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 函数仍然使用 AtomicCmpExchange,但在调用位置它通过参数而不是直接使用捕获的变量,并且在这些情况下生成的代码是正确的。

class function TInterlocked.CompareExchange(var Target: Integer; Value, Comparand: Integer): Integer;
begin
  Result := AtomicCmpExchange(Target, Value, Comparand);
end;

另一个不太理想的解决方案是使用 {$O-} 编译器指令关闭围绕损坏方法 Button1Click 的优化,然后使用 {$O+}

重新打开它

由于 AtomicCmpExchange 是 Delphi 内在函数,它的代码是在调用时由编译器直接生成的,错误的代码生成只会影响该过程,而不影响一般代码 - 换句话说,匿名方法捕获是在其他代码中正常工作(除非编译器中存在其他与此特定代码无关的错误)。

RTL中其他使用AtomicCmpExchange的地方,没有涉及变量捕获的代码,所以RTL、VCL和FMX代码不受此问题影响,可以在应用中开启优化.

注意:编译器中可能存在其他我们不知道的优化错误。