unit Myc.Core.Atomic; interface {$align on} uses {$ifndef NO_FASTMM} FastMM5, {$endif} Winapi.Windows; type TSListEntry = Winapi.Windows.TSListEntry; PSListEntry = Winapi.Windows.PSListEntry; PSList = ^TSList; TSList = packed record private FList: TSListHeader; public // Creates and initializes an aligned SList header. class function Create: PSList; static; // Initializes the SList header for use. procedure Init; // Frees the SList header. procedure Free; // Atomically pushes an entry onto the SList. function Push(ListEntry: PSListEntry): PSListEntry; // Atomically pops an entry from the SList. function Pop: PSListEntry; // Returns the current number of entries in the SList. function QueryDepth: Integer; end; TMycAtomicStack = record private type PItem = ^TItem; TItem = packed record Next: TSListEntry; Data: T; end; var FSList: PSList; public // Initializes the atomic stack, creating the underlying SList. class operator Initialize(out Dest: TMycAtomicStack); // Finalizes the atomic stack, freeing all items and the SList. class operator Finalize(var Dest: TMycAtomicStack); // Pops an item from the stack; returns Default(T) if empty. function Pop: T; inline; // Tries to pop an item from the stack; returns true if successful. function TryPop(out Item: T): Boolean; inline; // Pushes an item onto the stack. procedure Push(const Data: T); inline; procedure Clear; // Allocates an aligned memory block for a stack item. function Alloc: PItem; inline; // Pushes a pre-allocated item pointer onto the SList. procedure PushPtr(Item: PItem); inline; // Pops a raw item pointer from the SList. function PopPtr: PItem; inline; end; // Allocates memory with a specified alignment. procedure GetMemAligned(var P; Size: NativeUInt); // Frees memory allocated by GetMemAligned. procedure FreeMemAligned(var P); implementation const AlignmentBoundary = 16; AlignmentMask = AlignmentBoundary - 1; {$ifdef NO_FASTMM} const MetaDataSize = SizeOf(Pointer); procedure GetMemAligned(var P; Size: NativeUInt); var rawAllocatedPtr: Pointer; alignedUserPtrVal: NativeUInt; rawAllocatedPtrVal: NativeUInt; totalSizeToAllocate: NativeUInt; begin totalSizeToAllocate := Size + MetaDataSize + AlignmentMask; System.GetMem(rawAllocatedPtr, totalSizeToAllocate); if rawAllocatedPtr = nil then begin Pointer(P) := nil; exit; end; rawAllocatedPtrVal := NativeUInt(rawAllocatedPtr); alignedUserPtrVal := (rawAllocatedPtrVal + MetaDataSize + AlignmentMask) and not AlignmentMask; Pointer(P) := Pointer(alignedUserPtrVal); PPointer(alignedUserPtrVal - MetaDataSize)^ := rawAllocatedPtr; Assert((NativeUInt(P) and AlignmentMask) = 0, 'GetMemAligned did not return an aligned pointer.'); end; procedure FreeMemAligned(var P); var alignedUserPtrVal: NativeUInt; rawAllocatedPtr: Pointer; ptrToRawAllocatedPtr: PPointer; begin if Pointer(P) = nil then exit; alignedUserPtrVal := NativeUInt(P); ptrToRawAllocatedPtr := PPointer(alignedUserPtrVal - MetaDataSize); rawAllocatedPtr := ptrToRawAllocatedPtr^; System.FreeMem(rawAllocatedPtr); Pointer(P) := nil; end; {$else} procedure GetMemAligned(var P; Size: NativeUInt); inline; begin FastMM_EnterMinimumAddressAlignment(maa16Bytes); try GetMem(Pointer(P), Size); finally FastMM_ExitMinimumAddressAlignment(maa16Bytes); end; end; procedure FreeMemAligned(var P); inline; begin FreeMem(Pointer(P)); Pointer(P) := nil; end; {$endif} { TSList } class function TSList.Create: PSList; begin GetMemAligned(Result, SizeOf(TSList)); Result.Init; end; procedure TSList.Free; var tempSelf: Pointer; begin tempSelf := Pointer(@Self); // @Self is valid here as Free is an instance method of a record FreeMemAligned(tempSelf); end; procedure TSList.Init; begin Assert((NativeUInt(@FList) and AlignmentMask) = 0, 'TSList.FList member not aligned'); InitializeSListHead(@FList); end; function TSList.Pop: PSListEntry; begin Result := InterlockedPopEntrySList(@FList); end; function TSList.Push(ListEntry: PSListEntry): PSListEntry; begin Assert((NativeUInt(ListEntry) and AlignmentMask) = 0, 'ListEntry to be pushed is not aligned'); Result := InterlockedPushEntrySList(@FList, ListEntry); end; function TSList.QueryDepth: Integer; begin Result := QueryDepthSList(@FList); end; { TMycAtomicStack } class operator TMycAtomicStack.Initialize(out Dest: TMycAtomicStack); begin // Use Dest to access fields of the record instance being initialized Dest.FSList := TSList.Create; end; class operator TMycAtomicStack.Finalize(var Dest: TMycAtomicStack); begin Dest.Clear; if Dest.FSList <> nil then // Check if FSList was initialized begin Dest.FSList.Free; // Call instance method Free on FSList via Dest end; end; function TMycAtomicStack.Alloc: PItem; begin // This is an instance method, FSList is accessed directly (implicitly Self.FSList) GetMemAligned(Pointer(Result), SizeOf(TItem)); FillChar(Result^, SizeOf(TItem), 0); Result.Data := Default(T); end; procedure TMycAtomicStack.Clear; var item: PItem; begin // Use Dest to access fields and call instance methods // on the record instance being finalized item := PopPtr; // Call instance method PopPtr via Dest while item <> nil do begin item.Data := Default(T); FreeMemAligned(item); // Global procedure item := PopPtr; // Call instance method PopPtr via Dest end; end; function TMycAtomicStack.Pop: T; begin // Instance method if not TryPop(Result) then begin Result := Default(T); end; end; function TMycAtomicStack.PopPtr: PItem; begin // Instance method, FSList is Self.FSList Result := PItem(FSList.Pop); end; procedure TMycAtomicStack.Push(const Data: T); var item: PItem; begin // Instance method item := Alloc; // Calls Self.Alloc item.Data := Data; PushPtr(item); // Calls Self.PushPtr end; procedure TMycAtomicStack.PushPtr(Item: PItem); begin // Instance method, FSList is Self.FSList FSList.Push(PSListEntry(Item)); end; function TMycAtomicStack.TryPop(out Item: T): Boolean; var currentPItem: PItem; begin // Instance method currentPItem := PopPtr; // Calls Self.PopPtr Result := currentPItem <> nil; if Result then begin Item := currentPItem.Data; currentPItem.Data := Default(T); FreeMemAligned(currentPItem); end else begin Item := Default(T); end; end; end.