Why am I getting
[DCC Error] ProjectCOWArray.dpr(23): E2426 Inline function must not have asm block
program ProjectCOWArray;
{$APPTYPE CONSOLE}
{$R *.res}
type
PRefCount = ^TRefCount;
TRefCount = array[0..1] of integer;
TCOWArray<T> = record
private
fData: TArray<T>;
private
procedure IncRefCount; <<-- not inline
end;
{ TCOWArray<T> }
procedure TCOWArray<T>.IncRefCount;
asm
{$if defined(win32)}
mov eax,fData;
lock inc dword ptr [eax - 8];
{$ifend}
{$if defined(win64)}
mov rax,fData;
lock inc dword ptr[rax -12];
{$ifend}
end;
begin
end.
Delphi XE2 does not have AtomicIncrement
, so how do I solve this issue?
It'd like to retain the assembler, because otherwise I cannot have the lock
prefix in and I do not want to use InterlockedIncrement
because that's a WinAPI function and I do not want that kind of overhead.
This is because the generics functionality is implemented on top of the inlining engine. The same restrictions that apply to inline functions apply to generic functions. The compiler writers just haven't taken the extra step to make the error messages specific to generics rather than inline functions.
I think that calling InterlockedIncrement
is probably your best option, for Delphi versions that don't have the AtomicIncrement
intrinsic. Or, alternatively, create your own version of AtomicIncrement
, that is only defined in versions of Delphi that do not include it. And that function can be written in asm. Well, it clearly has to be written in asm of course.
{$IFNDEF AtomicFunctionsAvailable}
function AtomicIncrement(var Target: Integer): Integer;
asm
....
end;
{$ENDIF}
Or as @TLama suggests, you can use TInterlocked
from the System.SyncObjs
unit to provide atomic operations.
With all that said, I see no need to meddle with the internals in this way. Implement a copy of write array by calling SetLength(...)
whenever you write to the array. For instance, here's a very simple copy on write array implementation:
unit COWArray;
interface
type
TCOWArray<T> = record
private
FItems: TArray<T>;
function GetLength: Integer;
procedure SetLength(Value: Integer);
function GetItem(Index: Integer): T;
procedure SetItem(Index: Integer; const Value: T);
public
class function New(const Values: array of T): TCOWArray<T>; static;
property Length: Integer read GetLength write SetLength;
property Items[Index: Integer]: T read GetItem write SetItem; default;
end;
implementation
function TCOWArray<T>.GetLength: Integer;
begin
Result := System.Length(FItems);
end;
procedure TCOWArray<T>.SetLength(Value: Integer);
begin
System.SetLength(FItems, Value); // SetLength enforces uniqueness
end;
function TCOWArray<T>.GetItem(Index: Integer): T;
begin
Result := FItems[Index];
end;
procedure TCOWArray<T>.SetItem(Index: Integer; const Value: T);
begin
System.SetLength(FItems, System.Length(FItems)); // SetLength enforces uniqueness
FItems[Index] := Value;
end;
class function TCOWArray<T>.New(const Values: array of T): TCOWArray<T>;
var
i: Integer;
begin
System.SetLength(Result.FItems, System.Length(Values));
for i := 0 to high(Values) do
Result.FItems[i] := Values[i];
end;
end.
Even if you could call InterlockedIncrement
, you'd still have the obstacle of attaining a reference to the array's reference-count field. You don't really need it, though. Instead, you can let the compiler do the work for you. Declare another array variable and assign to it to increment the count. Then type-cast it to a non-managed type and set it back to a null pointer. The reference count will remain intact even after the variable goes out of scope.
var
dummy: TArray<T>;
begin
dummy := fData; // increment reference count
Pointer(dummy) := nil; // circumvent decrement
end;
This technique works for incrementing the reference count of anything, including arrays, strings, and interfaces.
To decrement is just one line:
begin
Pointer(dummy) := fData;
// reference count decrements automatically at scope's end
end;
Thanks David for explaining the all important why
A possible solution is to lift the AtomicIncrement out of the generic record and into a hack.
program ProjectCOWArray;
{$APPTYPE CONSOLE}
{$R *.res}
type
PRefCount = ^TRefCount;
TRefCount = array[0..1] of integer;
TCOWArray<T> = record
private
fData: TArray<T>;
private
procedure IncRefCount;
end;
{ TCOWArray<T> }
type TAnyOldDynArray = array of integer;
procedure AtomicIncrementDynArrayRefCount(const AnArray: TAnyOldDynArray);
asm
{$if defined(win32)}
mov eax,AnArray;
lock inc dword ptr [eax - 8];
{$else}
mov rax,AnArray;
lock inc dword ptr [rax -12];
{$ifend}
end;
procedure TCOWArray<T>.IncRefCount;
begin
AtomicIncrementDynArrayRefCount(TAnyOldDynArray(fData));
end;
begin
end.