/// Framework Core Shared Types and RTL-like Functions // - this unit is a part of the Open Source Synopse mORMot framework 2, // licensed under a MPL/GPL/LGPL three license - see LICENSE.md unit mormot.core.base; { ***************************************************************************** Basic types and reusable stand-alone functions shared by all framework units - Framework Version and Information - Common Types Used for Compatibility Between Compilers and CPU - Numbers (floats and integers) Low-level Definitions - Integer Arrays Manipulation - ObjArray PtrArray InterfaceArray Wrapper Functions - Low-level Types Mapping Binary or Bits Structures - Buffers (e.g. Hashing and SynLZ compression) Raw Functions - Efficient Variant Values Conversion - Sorting/Comparison Functions - Some Convenient TStream descendants and File access functions - Faster Alternative to RTL Standard Functions - Raw Shared Constants / Types Definitions Aim of those types and functions is to be cross-platform and cross-compiler, without any dependency but the main FPC/Delphi RTL. It also detects the kind of CPU it runs on, to adapt to the fastest asm version available. It is the main unit where x86_64 or i386 asm stubs are included. ***************************************************************************** } interface {$I mormot.defines.inc} uses variants, classes, contnrs, types, sysutils; { ************ Framework Version and Information } const /// the full text of the Synopse mORMot framework // - note: we don't supply full version number with build revision for // HTTP servers, to reduce potential attack surface SYNOPSE_FRAMEWORK_NAME = 'mORMot'; /// the corresponding version of the mORMot framework, as '2.#.#' // - 2nd digit is minor version, increased at each framework release, // when adding functionality in a stable enough manner // - 3rd digit is a globally increasing git commit number (as generated by the // commit.sh script) - so won't be reset when minor is up SYNOPSE_FRAMEWORK_VERSION = {$I mormot.commit.inc}; /// a text including the version and the main active conditional options // - usefull for low-level debugging purpose SYNOPSE_FRAMEWORK_FULLVERSION = SYNOPSE_FRAMEWORK_VERSION {$ifdef FPC} {$ifdef FPC_X64MM} + ' x64MM' {$ifdef FPCMM_BOOST} + 'b' {$endif} {$ifdef FPCMM_SERVER} + 's' {$endif} {$else} {$ifdef FPC_LIBCMM} + ' CM' {$endif} {$endif FPC_X64MM} {$else} {$ifdef FullDebugMode} + ' FDM' {$endif} {$endif FPC}; { ************ Common Types Used for Compatibility Between Compilers and CPU } const /// internal Code Page for UTF-8 Unicode encoding // - as used by RawUtf8 and all our internal framework text process CP_UTF8 = 65001; /// internal Code Page for UTF-16 Unicode encoding // - used e.g. for Delphi 2009+ UnicodeString=String type CP_UTF16 = 1200; /// internal Code Page for RawByteString undefined string CP_RAWBYTESTRING = 65535; /// fake code page used to recognize RawBlob // - RawBlob internal code page will be CP_RAWBYTESTRING = 65535, but our ORM // will identify the RawBlob type and unserialize it using CP_RAWBLOB instead // - TJsonWriter.AddAnyAnsiBuffer will recognize it and use Base-64 encoding CP_RAWBLOB = 65534; /// US English Windows Code Page, i.e. WinAnsi standard character encoding CP_WINANSI = 1252; /// Latin-1 ISO/IEC 8859-1 Code Page // - map low 8-bit Unicode CodePoints CP_LATIN1 = 819; /// internal Code Page for System AnsiString encoding CP_ACP = 0; /// internal Code Page for System Console encoding CP_OEM = 1; /// use rather CP_WINANSI with mORMot 2 CODEPAGE_US = CP_WINANSI; /// use rather CP_LATIN1 with mORMot 2 CODEPAGE_LATIN1 = CP_LATIN1; {$ifdef FPC} { make cross-compiler and cross-CPU types available to Delphi } type PBoolean = ^boolean; {$else FPC} type {$ifdef CPU64} // Delphi XE2 seems stable about those types (not Delphi 2009) PtrInt = NativeInt; PtrUInt = NativeUInt; {$else} /// a CPU-dependent signed integer type cast of a pointer / register // - used for 64-bit compatibility, native under Free Pascal Compiler PtrInt = integer; /// a CPU-dependent unsigned integer type cast of a pointer / register // - used for 64-bit compatibility, native under Free Pascal Compiler PtrUInt = cardinal; {$endif CPU64} /// a CPU-dependent unsigned integer type cast of a pointer of pointer // - used for 64-bit compatibility, native under Free Pascal Compiler PPtrUInt = ^PtrUInt; /// a CPU-dependent signed integer type cast of a pointer of pointer // - used for 64-bit compatibility, native under Free Pascal Compiler PPtrInt = ^PtrInt; /// unsigned Int64 doesn't exist under older Delphi, but is defined in FPC // - and UInt64 is buggy as hell under Delphi 2007 when inlining functions: // older compilers will fallback to signed Int64 values // - anyway, consider using SortDynArrayQWord() to compare QWord values // in a safe and efficient way, under a CPUX86 // - use UInt64 explicitly in your computation (like in mormot.crypt.ecc), // if you are sure that Delphi 6-2007 compiler handles your code as expected, // but mORMot code will expect to use QWord for its internal process // (e.g. ORM/SOA serialization) {$ifdef UNICODE} QWord = UInt64; {$else} QWord = type Int64; {$endif UNICODE} /// points to an unsigned Int64 PQWord = ^QWord; // redefined here to not use the unexpected PWord definition from Windows unit PWord = System.PWord; // redefined here to not use the unexpected PSingle definition from Windows unit PSingle = System.PSingle; // this pointer is not defined on older Delphi revisions PMethod = ^TMethod; {$ifndef ISDELPHIXE2} /// used to store the handle of a system Thread TThreadID = cardinal; /// compatibility definition with FPC and newer Delphi PUInt64 = ^UInt64; {$endif ISDELPHIXE2} {$endif FPC} type /// RawUtf8 is an UTF-8 String stored in an AnsiString, alias to System.UTF8String // - all conversion to/from string or WinAnsiString must be explicit on // Delphi 7/2007, and it will be faster anyway to use our optimized functions // from mormot.core.unicode.pas unit like StringToUtf8/Utf8ToString RawUtf8 = System.UTF8String; // CP_UTF8 Codepage /// a RawUtf8 value which may contain Sensitive Personal Information // (e.g. a bank card number or a plain password) // - identified as a specific type e.g. to be hidden in the logs - when the // woHideSensitivePersonalInformation TTextWriterWriteObjectOption is set SpiUtf8 = type RawUtf8; /// WinAnsiString is a WinAnsi-encoded AnsiString (code page 1252) // - use this type instead of System.String, which behavior changed // between Delphi 2009 compiler and previous versions: our implementation // is consistent and compatible with all versions of Delphi compiler // - all conversion to/from string or RawUtf8/UTF8String must be explicit on // Delphi 7/2007, and it will be faster anyway to use our optimized functions // from mormot.core.unicode.pas unit like StringToUtf8/Utf8ToString {$ifdef HASCODEPAGE} WinAnsiString = type AnsiString(CP_WINANSI); // WinAnsi 1252 Codepage {$else} WinAnsiString = type AnsiString; {$endif HASCODEPAGE} {$ifdef HASCODEPAGE} {$ifdef FPC} // missing declaration PRawByteString = ^RawByteString; {$endif FPC} {$else} /// define RawByteString, as it does exist in Delphi 2009+ // - to be used for byte storage into an AnsiString // - use this type if you don't want the Delphi compiler not to do any // code page conversions when you assign a typed AnsiString to a RawByteString, // i.e. a RawUtf8 or a WinAnsiString RawByteString = type AnsiString; /// pointer to a RawByteString PRawByteString = ^RawByteString; {$endif HASCODEPAGE} /// RawJson will indicate that this variable content would stay as raw JSON // - i.e. won't be serialized into values // - could be any JSON content: number, boolean, null, string, object or array // - e.g. interface-based service will use it for efficient and AJAX-ready // transmission of TOrmTableJson result RawJson = type RawUtf8; /// a RawByteString sub-type used to store the BLOB content in our ORM // - equals RawByteString for byte storage // - TRttiInfo.AnsiStringCodePage will identify this type, and return // CP_RAWBLOB fake codepage for such a published property, even if it is // just an alias to CP_RAWBYTESTRING // - our ORM will therefore identify such properties as BLOB // - by default, the BLOB fields are not retrieved or updated with raw // TRest.Retrieve() method, that is "Lazy loading" is enabled by default // for blobs, unless TRestClientUri.ForceBlobTransfert property is TRUE // (for all tables), or ForceBlobTransfertTable[] (for a particular table); // so use RetrieveBlob() methods for handling BLOB fields // - could be defined as value in a TOrm property as such: // ! property Blob: RawBlob read fBlob write fBlob; // - is defined here for proper TRttiProp.WriteAsJson serialization RawBlob = type RawByteString; /// SynUnicode is the fastest available Unicode native string type, depending // on the compiler used // - this type is native to the compiler, so you can use Length() Copy() and // such functions with it (this is not possible with RawUnicodeString type) // - before Delphi 2009+, it uses slow OLE compatible WideString // (with our Enhanced RTL, WideString allocation can be made faster by using // an internal caching mechanism of allocation buffers - WideString allocation // has been made much faster since Windows Vista/Seven) // - starting with Delphi 2009, it uses the faster UnicodeString type, which // allow Copy On Write, Reference Counting and fast heap memory allocation // - on recent FPC, HASVARUSTRING is defined and native UnicodeString is set {$ifdef HASVARUSTRING} SynUnicode = UnicodeString; {$else} SynUnicode = WideString; {$endif HASVARUSTRING} {$ifndef PUREMORMOT2} /// low-level RawUnicode as an Unicode String stored in an AnsiString // - DEPRECATED TYPE, introduced in Delphi 7/2007 days: SynUnicode is to be used // - faster than WideString, which are allocated in Global heap (for COM) // - an AnsiChar(#0) is added at the end, for having a true WideChar(#0) at ending // - length(RawUnicode) returns memory bytes count: use (length(RawUnicode) shr 1) // for WideChar count (that's why the definition of this type since Delphi 2009 // is AnsiString(1200) and not UnicodeString) // - pointer(RawUnicode) is compatible with Win32 'Wide' API call // - mimic Delphi 2009 UnicodeString, without the WideString or Ansi conversion overhead // - all conversion to/from AnsiString or RawUtf8 must be explicit: the // compiler may not be able to perform implicit conversions on CP_UTF16 {$ifdef HASCODEPAGE} RawUnicode = type AnsiString(CP_UTF16); // Codepage for an "Unicode" String {$else} RawUnicode = type AnsiString; {$endif HASCODEPAGE} PRawUnicode = ^RawUnicode; {$endif PUREMORMOT2} /// low-level storage of UCS4 CodePoints, stored as 32-bit integers RawUcs4 = TIntegerDynArray; /// store one 32-bit UCS4 CodePoint (with a better naming than UCS4 "Char") // - RTL's Ucs4Char is buggy, especially on oldest Delphi Ucs4CodePoint = cardinal; {$ifdef CPU64} HalfInt = integer; HalfUInt = cardinal; {$else} /// a CPU-dependent signed integer type cast of half a pointer HalfInt = smallint; /// a CPU-dependent unsigned integer type cast of half a pointer HalfUInt = word; {$endif CPU64} /// a CPU-dependent signed integer type cast of a pointer to half a pointer PHalfInt = ^HalfInt; /// a CPU-dependent unsigned integer type cast of a pointer to half a pointer PHalfUInt = ^HalfUInt; PRawJson = ^RawJson; PPRawJson = ^PRawJson; PRawUtf8 = ^RawUtf8; PPRawUtf8 = ^PRawUtf8; PWinAnsiString = ^WinAnsiString; PWinAnsiChar = type PAnsiChar; PSynUnicode = ^SynUnicode; PFileName = ^TFileName; /// a simple wrapper to UTF-8 encoded zero-terminated PAnsiChar // - PAnsiChar is used only for Win-Ansi encoded text // - the Synopse mORMot framework uses mostly this PUtf8Char type, // because all data is internally stored and expected to be UTF-8 encoded PUtf8Char = type PAnsiChar; PPUtf8Char = ^PUtf8Char; PPPUtf8Char = ^PPUtf8Char; /// a Row/Col array of PUtf8Char, for containing sqlite3_get_table() result TPUtf8CharArray = array[ 0 .. MaxInt div SizeOf(PUtf8Char) - 1 ] of PUtf8Char; PPUtf8CharArray = ^TPUtf8CharArray; /// a dynamic array of PUtf8Char pointers TPUtf8CharDynArray = array of PUtf8Char; /// a dynamic array of UTF-8 encoded strings TRawUtf8DynArray = array of RawUtf8; PRawUtf8DynArray = ^TRawUtf8DynArray; TRawUtf8DynArrayDynArray = array of TRawUtf8DynArray; /// a dynamic array of TVarRec, i.e. could match an "array of const" parameter TTVarRecDynArray = array of TVarRec; /// a TVarData values array // - is not called TVarDataArray to avoid confusion with the corresponding // type already defined in RTL Variants.pas, and used for custom late-binding TVarDataStaticArray = array[ 0 .. MaxInt div SizeOf(TVarData) - 1 ] of TVarData; PVarDataStaticArray = ^TVarDataStaticArray; TVariantArray = array[ 0 .. MaxInt div SizeOf(Variant) - 1 ] of Variant; PVariantArray = ^TVariantArray; TVariantDynArray = array of variant; PPVariant = ^PVariant; PPVarData = ^PVarData; PIntegerDynArray = ^TIntegerDynArray; TIntegerDynArray = array of integer; TIntegerDynArrayDynArray = array of TIntegerDynArray; PCardinalDynArray = ^TCardinalDynArray; TCardinalDynArray = array of cardinal; PSingleDynArray = ^TSingleDynArray; TSingleDynArray = array of Single; PInt64DynArray = ^TInt64DynArray; TInt64DynArray = array of Int64; PQwordDynArray = ^TQwordDynArray; TQwordDynArray = array of Qword; TPtrUIntDynArray = array of PtrUInt; THalfUIntDynArray = array of HalfUInt; PDoubleDynArray = ^TDoubleDynArray; TDoubleDynArray = array of double; PCurrencyDynArray = ^TCurrencyDynArray; TCurrencyDynArray = array of currency; PExtendedDynArray = ^TExtendedDynArray; TExtendedDynArray = array of Extended; TWordDynArray = array of word; PWordDynArray = ^TWordDynArray; TByteDynArray = array of byte; PByteDynArray = ^TByteDynArray; {$ifndef ISDELPHI2007ANDUP} TBytes = array of byte; {$endif ISDELPHI2007ANDUP} TBytesDynArray = array of TBytes; PBytesDynArray = ^TBytesDynArray; TObjectDynArray = array of TObject; PObjectDynArray = ^TObjectDynArray; TPersistentDynArray = array of TPersistent; PPersistentDynArray = ^TPersistentDynArray; TPointerDynArray = array of pointer; PPointerDynArray = ^TPointerDynArray; TPointerDynArrayDynArray = array of TPointerDynArray; TPPointerDynArray = array of PPointer; PPPointerDynArray = ^TPPointerDynArray; TMethodDynArray = array of TMethod; PMethodDynArray = ^TMethodDynArray; TObjectListDynArray = array of TObjectList; PObjectListDynArray = ^TObjectListDynArray; TFileNameDynArray = array of TFileName; PFileNameDynArray = ^TFileNameDynArray; TBooleanDynArray = array of boolean; PBooleanDynArray = ^TBooleanDynArray; TClassDynArray = array of TClass; TWinAnsiDynArray = array of WinAnsiString; PWinAnsiDynArray = ^TWinAnsiDynArray; TStringDynArray = array of string; PStringDynArray = ^TStringDynArray; PShortStringDynArray = array of PShortString; PPShortStringArray = ^PShortStringArray; TShortStringDynArray = array of ShortString; TDateTimeDynArray = array of TDateTime; PDateTimeDynArray = ^TDateTimeDynArray; {$ifndef FPC_OR_UNICODE} TDate = type TDateTime; TTime = type TDateTime; {$endif FPC_OR_UNICODE} TDateDynArray = array of TDate; PDateDynArray = ^TDateDynArray; TTimeDynArray = array of TTime; PTimeDynArray = ^TTimeDynArray; TWideStringDynArray = array of WideString; PWideStringDynArray = ^TWideStringDynArray; TSynUnicodeDynArray = array of SynUnicode; PSynUnicodeDynArray = ^TSynUnicodeDynArray; TRawByteStringDynArray = array of RawByteString; PRawByteStringDynArray = ^TRawByteStringDynArray; {$ifdef HASVARUSTRING} TUnicodeStringDynArray = array of UnicodeString; PUnicodeStringDynArray = ^TUnicodeStringDynArray; {$endif HASVARUSTRING} TRawJsonDynArray = array of RawJson; PRawJsonDynArray = ^TRawJsonDynArray; TGuidDynArray = array of TGuid; PGuidDynArray = array of PGuid; PObject = ^TObject; PClass = ^TClass; PList = ^TList; PObjectList = ^TObjectList; PCollection = ^TCollection; PStrings = ^TStrings; PPByte = ^PByte; PPPByte = ^PPByte; PPInteger = ^PInteger; PPCardinal = ^PCardinal; PPPointer = ^PPointer; PByteArray = ^TByteArray; TByteArray = array[ 0 .. MaxInt - 1 ] of byte; // redefine here with {$R-} PBooleanArray = ^TBooleanArray; TBooleanArray = array[ 0 .. MaxInt - 1 ] of boolean; PPWord = ^PWord; TWordArray = array[ 0 .. MaxInt div SizeOf(word) - 1 ] of word; PWordArray = ^TWordArray; TIntegerArray = array[ 0 .. MaxInt div SizeOf(integer) - 1 ] of integer; PIntegerArray = ^TIntegerArray; PIntegerArrayDynArray = array of PIntegerArray; TPIntegerArray = array[ 0 .. MaxInt div SizeOf(PIntegerArray) - 1 ] of PInteger; PPIntegerArray = ^TPIntegerArray; TCardinalArray = array[ 0 .. MaxInt div SizeOf(cardinal) - 1 ] of cardinal; PCardinalArray = ^TCardinalArray; TInt64Array = array[ 0 .. MaxInt div SizeOf(Int64) - 1 ] of Int64; PInt64Array = ^TInt64Array; TQWordArray = array[ 0 .. MaxInt div SizeOf(QWord) - 1 ] of QWord; PQWordArray = ^TQWordArray; TPtrUIntArray = array[ 0 .. MaxInt div SizeOf(PtrUInt) - 1 ] of PtrUInt; PPtrUIntArray = ^TPtrUIntArray; THalfUIntArray = array[ 0 .. MaxInt div SizeOf(HalfUInt) - 1 ] of HalfUInt; PHalfUIntArray = ^THalfUIntArray; TSmallIntArray = array[ 0 .. MaxInt div SizeOf(SmallInt) - 1 ] of SmallInt; PSmallIntArray = ^TSmallIntArray; TSingleArray = array[ 0 .. MaxInt div SizeOf(Single) - 1 ] of Single; PSingleArray = ^TSingleArray; TDoubleArray = array[ 0 .. MaxInt div SizeOf(Double) - 1 ] of Double; PDoubleArray = ^TDoubleArray; TDateTimeArray = array[ 0 .. MaxInt div SizeOf(TDateTime) - 1 ] of TDateTime; PDateTimeArray = ^TDateTimeArray; TPAnsiCharArray = array[ 0 .. MaxInt div SizeOf(PAnsiChar) - 1 ] of PAnsiChar; PPAnsiCharArray = ^TPAnsiCharArray; TRawUtf8Array = array[ 0 .. MaxInt div SizeOf(RawUtf8) - 1 ] of RawUtf8; PRawUtf8Array = ^TRawUtf8Array; TRawByteStringArray = array[ 0 .. MaxInt div SizeOf(RawByteString) - 1 ] of RawByteString; PRawByteStringArray = ^TRawByteStringArray; PShortStringArray = array[ 0 .. MaxInt div SizeOf(pointer) - 1 ] of PShortString; TPointerArray = array[ 0 .. MaxInt div SizeOf(Pointer) - 1 ] of Pointer; PPointerArray = ^TPointerArray; TClassArray = array[ 0 .. MaxInt div SizeOf(TClass) - 1 ] of TClass; PClassArray = ^TClassArray; TObjectArray = array[ 0 .. MaxInt div SizeOf(TObject) - 1 ] of TObject; PObjectArray = ^TObjectArray; TPtrIntArray = array[ 0 .. MaxInt div SizeOf(PtrInt) - 1 ] of PtrInt; PPtrIntArray = ^TPtrIntArray; PInt64Rec = ^Int64Rec; PLongRec = ^LongRec; PPShortString = ^PShortString; PTextFile = ^TextFile; PInterface = ^IInterface; TInterfaceDynArray = array of IInterface; PInterfaceDynArray = ^TInterfaceDynArray; TStreamClass = class of TStream; TInterfacedObjectClass = class of TInterfacedObject; TListClass = class of TList; TObjectListClass = class of TObjectList; TCollectionClass = class of TCollection; TCollectionItemClass = class of TCollectionItem; ExceptionClass = class of Exception; {$M+} ExceptionWithProps = class(Exception); // not as good as ESynException {$M-} type /// used e.g. to serialize up to 256-bit as hexadecimal TShort64 = string[64]; PShort64 = ^TShort64; /// a shortstring which only takes 48 bytes of memory TShort47 = string[47]; PShort47 = ^TShort47; /// used e.g. for SetThreadName/GetCurrentThreadName TShort31 = string[31]; PShort31 = ^TShort31; /// used e.g. by PointerToHexShort/CardinalToHexShort/Int64ToHexShort/FormatShort16 // - such result type would avoid a string allocation on heap, so are highly // recommended e.g. when logging small pieces of information TShort16 = string[16]; PShort16 = ^TShort16; /// used e.g. for TTextWriter.AddShorter small text constants TShort8 = string[8]; PShort8 = ^TShort8; /// stack-allocated ASCII string, used by GuidToShort() function TGuidShortString = string[38]; /// cross-compiler type used for string length // - FPC uses PtrInt/SizeInt, Delphi uses longint even on CPU64 TStrLen = {$ifdef FPC} SizeInt {$else} longint {$endif}; /// pointer to cross-compiler type used for string length PStrLen = ^TStrLen; /// cross-compiler type used for dynamic array length // - both FPC and Delphi uses PtrInt/NativeInt for dynamic array high/length TDALen = PtrInt; /// pointer to cross-compiler type used for dynamic array length PDALen = ^TDALen; /// cross-compiler type used for string reference counter // - FPC and Delphi don't always use the same type TStrCnt = {$ifdef STRCNT32} integer {$else} SizeInt {$endif}; /// pointer to cross-compiler type used for string reference counter PStrCnt = ^TStrCnt; /// cross-compiler type used for dynarray reference counter // - FPC uses PtrInt/SizeInt, Delphi uses longint even on CPU64 TDACnt = {$ifdef DACNT32} integer {$else} SizeInt {$endif}; /// pointer to cross-compiler type used for dynarray reference counter PDACnt = ^TDACnt; /// cross-compiler return type of IUnknown._AddRef/_Release methods // - used to reduce the $ifdef when implementing interfaces in Delphi and FPC TIntCnt = {$ifdef FPC} longint {$else} integer {$endif}; /// cross-compiler return type of IUnknown.QueryInterface method // - used to reduce the $ifdef when implementing interfaces in Delphi and FPC TIntQry = {$ifdef FPC} longint {$else} HRESULT {$endif}; {$ifdef FPC} TStrRec = record // see TAnsiRec/TUnicodeRec in astrings/ustrings.inc case integer of 0: ( {$ifdef HASCODEPAGE} codePage: TSystemCodePage; // =Word elemSize: Word; {$ifndef STRCNT32} {$ifdef CPU64} _PaddingToQWord: DWord; {$endif CPU64} {$endif STRCNT32} {$endif HASCODEPAGE} refCnt: TStrCnt; // =SizeInt on older FPC, =longint since FPC 3.4 length: TStrLen; ); {$ifdef HASCODEPAGE} 1: ( codePageElemSize: cardinal; ); {$endif HASCODEPAGE} end; TDynArrayRec = record refCnt: TDACnt; // =SizeInt high: TDALen; // =SizeInt (differs from Delphi: equals length-1) function GetLength: TDALen; inline; procedure SetLength(len: TDALen); inline; property length: TDALen // Delphi compatibility wrapper read GetLength write SetLength; end; {$else not FPC} /// map the Delphi/FPC string header (stored before each instance) TStrRec = packed record {$ifdef HASCODEPAGE} {$ifdef CPU64} /// padding bytes for 16 byte alignment of the header _Padding: cardinal; {$endif CPU64} /// the string code page - e.g. CP_UTF8 for RawUtf8 codePage: Word; /// 1 for AnsiString/RawByteString/RawUtf8, 2 for UnicodeString elemSize: Word; {$endif HASCODEPAGE} /// string reference count (basic garbage memory mechanism) refCnt: TStrCnt; // 32-bit longint with Delphi /// equals length(s) - i.e. size in AnsiChar/WideChar, not bytes length: TStrLen; // 32-bit longint with Delphi end; /// map the Delphi/FPC dynamic array header (stored before each instance) TDynArrayRec = packed record {$ifdef CPUX64} /// padding bytes for 16 byte alignment of the header _Padding: cardinal; {$endif} /// dynamic array reference count (basic garbage memory mechanism) refCnt: TDACnt; // 32-bit longint with Delphi /// length in element count // - size in bytes = length*ElemSize length: TDALen; // PtrInt/NativeInt end; {$endif FPC} PStrRec = ^TStrRec; PDynArrayRec = ^TDynArrayRec; const /// codePage offset = string header size // - used to calc the beginning of memory allocation of a string _STRRECSIZE = SizeOf(TStrRec); /// cross-compiler negative offset to TStrRec.length field // - to be used inlined e.g. as PStrLen(p - _STRLEN)^ _STRLEN = SizeOf(TStrLen); /// cross-compiler negative offset to TStrRec.refCnt field // - to be used inlined e.g. as PStrCnt(p - _STRCNT)^ _STRCNT = SizeOf(TStrCnt) + _STRLEN; /// used to calc the beginning of memory allocation of a dynamic array _DARECSIZE = SizeOf(TDynArrayRec); /// cross-compiler negative offset to TDynArrayRec.high/length field // - to be used inlined e.g. as // ! PDALen(PAnsiChar(Values) - _DALEN)^ + _DAOFF // - both FPC and Delphi uses PtrInt/NativeInt for dynamic array high/length _DALEN = SizeOf(TDALen); /// cross-compiler adjuster to get length from TDynArrayRec.high/length field _DAOFF = {$ifdef FPC} 1 {$else} 0 {$endif}; /// cross-compiler negative offset to TDynArrayRec.refCnt field // - to be used inlined e.g. as PDACnt(PAnsiChar(Values) - _DACNT)^ _DACNT = SizeOf(TDACnt) + _DALEN; /// in-memory string process will allow up to 800 MB // - used as high limit e.g. for TBufferWriter over a TRawByteStringStream // - Delphi strings have a 32-bit length so you should change your algorithm // - even if FPC on CPU64 can handle bigger strings, consider other patterns _STRMAXSIZE = $5fffffff; /// in-memory TBytes process will allow up to 800 MB // - used as high limit e.g. for TBufferWriter.FlushToBytes // - even if a dynamic array can handle PtrInt length, consider other patterns _DAMAXSIZE = $5fffffff; /// like SetLength() but without any memory resize - WARNING: len should be > 0 procedure DynArrayFakeLength(arr: pointer; len: TDALen); {$ifdef HASINLINE} inline; {$endif} {$ifndef CPUARM} type /// used as ToByte() to properly truncate any integer into 8-bit // - is defined as an inlined "and 255" function under ARM to work as expected ToByte = byte; {$else} function ToByte(value: cardinal): cardinal; inline; {$endif CPUARM} const /// used to mark the end of ASCIIZ buffer, or return a void ShortString NULCHAR: AnsiChar = #0; /// a TGuid containing '{00000000-0000-0000-0000-00000000000}' GUID_NULL: TGuid = ({%H-}); NULL_LOW = ord('n') + ord('u') shl 8 + ord('l') shl 16 + ord('l') shl 24; FALSE_LOW = ord('f') + ord('a') shl 8 + ord('l') shl 16 + ord('s') shl 24; FALSE_LOW2 = ord('a') + ord('l') shl 8 + ord('s') shl 16 + ord('e') shl 24; TRUE_LOW = ord('t') + ord('r') shl 8 + ord('u') shl 16 + ord('e') shl 24; /// fill a TGuid with 0 procedure FillZero(var result: TGuid); overload; {$ifdef HASINLINE}inline;{$endif} /// compare two TGuid values // - this version is faster than the one supplied by SysUtils function IsEqualGuid({$ifdef FPC_HAS_CONSTREF}constref{$else}const{$endif} guid1, guid2: TGuid): boolean; overload; {$ifdef HASINLINE}inline;{$endif} /// compare two TGuid values // - this version is faster than the one supplied by SysUtils function IsEqualGuid(guid1, guid2: PGuid): boolean; overload; {$ifdef HASINLINE}inline;{$endif} /// returns the index of a matching TGuid in an array // - returns -1 if no item matched function IsEqualGuidArray(const guid: TGuid; const guids: array of TGuid): integer; /// check if a TGuid value contains only 0 bytes // - this version is faster than the one supplied by SysUtils function IsNullGuid({$ifdef FPC_HAS_CONSTREF}constref{$else}const{$endif} guid: TGuid): boolean; {$ifdef HASINLINE}inline;{$endif} /// append one TGuid item to a TGuid dynamic array // - returning the newly inserted index in guids[], or an existing index in // guids[] if NoDuplicates is TRUE and TGuid already exists function AddGuid(var guids: TGuidDynArray; const guid: TGuid; NoDuplicates: boolean = false): integer; /// compute a random UUid value from the RandomBytes() generator and RFC 4122 procedure RandomGuid(out result: TGuid); overload; /// compute a random UUid value from the RandomBytes() generator and RFC 4122 function RandomGuid: TGuid; overload; {$ifdef HASINLINE}inline;{$endif} /// compute the new capacity when expanding an array of items // - handle tiny, small, medium, large and huge sizes properly to reduce // memory usage and maximize performance // - initial steps are 4, 8, 12, 28, 40, 56, 72, 88, 104, 120, 136, 170, 212, // 265, 331, 413, 516, 645, 806, 1007, 1258, 1572, ... function NextGrow(capacity: integer): integer; /// equivalence to SetString(s,pansichar,len) function but from a raw pointer // - so works with both PAnsiChar and PUtf8Char input buffer (or even PByteArray) // - faster especially under FPC procedure FastSetString(var s: RawUtf8; p: pointer; len: PtrInt); overload; {$ifndef HASCODEPAGE} {$ifdef HASINLINE}inline;{$endif} {$endif} /// faster equivalence to SetString(s,nil,len) function procedure FastSetString(var s: RawUtf8; len: PtrInt); overload; {$ifndef HASCODEPAGE} {$ifdef HASINLINE}inline;{$endif} {$endif} /// equivalence to SetString(s,pansichar,len) function but from a raw pointer // - so works with both PAnsiChar and PUtf8Char input buffer (or even PByteArray) // - faster especially under FPC procedure FastSetRawByteString(var s: RawByteString; p: pointer; len: PtrInt); {$ifndef HASCODEPAGE} {$ifdef HASINLINE}inline;{$endif} {$endif} /// equivalence to SetString(s,nil,len) function to allocate a new RawByteString // - faster especially under FPC procedure FastNewRawByteString(var s: RawByteString; len: PtrInt); {$ifndef HASCODEPAGE} {$ifdef HASINLINE}inline;{$endif} {$endif} /// equivalence to SetString(s,pansichar,len) function with a specific code page // - faster especially under FPC procedure FastSetStringCP(var s; p: pointer; len, codepage: PtrInt); {$ifndef HASCODEPAGE} {$ifdef HASINLINE}inline;{$endif} {$endif} /// assign any constant or already ref-counted AnsiString/RawUtf8 // - by default, called with s = nil, is an equivalence to Finalize(d) or d := '' // - is also called by FastSetString/FastSetStringCP to setup its allocated value // - faster especially under FPC procedure FastAssignNew(var d; s: pointer = nil); {$ifndef FPC_CPUX64} {$ifdef HASINLINE}inline;{$endif} {$endif} /// internal function to assign any constant or ref-counted AnsiString/RawUtf8 // - caller should have tested that pointer(d) <> nil procedure FastAssignNewNotVoid(var d; s: pointer); overload; {$ifndef FPC_CPUX64}{$ifdef HASINLINE}inline;{$endif}{$endif} /// internal function used by FastSetString/FastSetStringCP function FastNewString(len, codepage: PtrInt): PAnsiChar; {$ifdef HASINLINE}inline;{$endif} /// ensure the supplied variable will have a CP_UTF8 - making it unique if needed procedure EnsureRawUtf8(var s: RawByteString); overload; {$ifdef HASINLINE}inline;{$endif} /// ensure the supplied variable will have a CP_UTF8 - making it unique if needed procedure EnsureRawUtf8(var s: RawUtf8); overload; {$ifdef HASINLINE}inline;{$endif} /// internal function which could be used instead of SetLength() if RefCnt = 1 procedure FakeLength(var s: RawUtf8; len: PtrInt); overload; {$ifdef HASINLINE} inline; {$endif} /// internal function which could be used instead of SetLength() if RefCnt = 1 procedure FakeLength(var s: RawUtf8; endChar: PUtf8Char); overload; {$ifdef HASINLINE} inline; {$endif} /// internal function which could be used instead of SetLength() if RefCnt = 1 procedure FakeLength(var s: RawByteString; len: PtrInt); overload; {$ifdef HASINLINE} inline; {$endif} /// internal function which could be used instead of SetLength() if RefCnt = 1 // - FakeLength() don't handle len = 0, whereas this function will procedure FakeSetLength(var s: RawUtf8; len: PtrInt); overload; /// internal function which could be used instead of SetLength() if RefCnt = 1 // - FakeLength() don't handle len = 0, whereas this function will procedure FakeSetLength(var s: RawByteString; len: PtrInt); overload; /// internal function which could be used instead of SetCodePage() if RefCnt = 1 // - do nothing if HASCODEPAGE is not defined, e.g. on Delphi 7-2007 // - warning: s should NOT be read-only (i.e. assigned from a constant), but // a just-allocated string with RefCnt <> -1 procedure FakeCodePage(var s: RawByteString; cp: cardinal); {$ifdef HASINLINE} inline; {$endif} /// internal function which assign src to dest, force CP_UTF8 and set src to '' // - warning: calls FakeCodePage(CP_UTF8) so requires src to have a RefCnt of 1 procedure FastAssignUtf8(var dest: RawUtf8; var src: RawByteString); {$ifdef HASINLINE} inline; {$endif} {$ifdef HASCODEPAGE} /// retrieve the code page of a non void string // - caller should have ensure that s <> '' function GetCodePage(const s: RawByteString): cardinal; inline; {$endif HASCODEPAGE} /// initialize a RawByteString, ensuring returned "aligned" pointer // is 16-bytes aligned // - to be used e.g. for proper SIMD process // - you can specify an alternate alignment, but it should be a power of two procedure GetMemAligned(var holder: RawByteString; fillwith: pointer; len: PtrUInt; out aligned: pointer; alignment: PtrUInt = 16); /// equivalence to @u[1] expression to ensure a RawUtf8 variable is unique // - will ensure that the string refcount is 1, and return a pointer to the text // - under FPC, @u[1] does not call UniqueString() as it does with Delphi // - if u is a constant (refcount=-1), will allocate a temporary copy in heap function UniqueRawUtf8(var u: RawUtf8): pointer; {$ifdef HASINLINE}inline;{$endif} /// direct conversion of an ANSI-7 ShortString into an AnsiString // - can be used e.g. for names retrieved from RTTI to convert them into RawUtf8 function ShortStringToAnsi7String(const source: ShortString): RawByteString; overload; {$ifdef HASINLINE}inline;{$endif} /// direct conversion of an ANSI-7 ShortString into an AnsiString // - can be used e.g. for names retrieved from RTTI to convert them into RawUtf8 procedure ShortStringToAnsi7String(const source: ShortString; var result: RawUtf8); overload; {$ifdef HASINLINE}inline;{$endif} /// direct conversion of an ANSI-7 AnsiString into an ShortString // - can be used e.g. for names retrieved from RTTI procedure Ansi7StringToShortString(const source: RawUtf8; var result: ShortString); {$ifdef FPC}inline;{$endif} /// simple concatenation of a 32-bit unsigned integer as text into a shorstring procedure AppendShortCardinal(value: cardinal; var dest: ShortString); /// simple concatenation of a 64-bit integer as text into a shorstring procedure AppendShortInt64(value: Int64; var dest: ShortString); /// simple concatenation of a character into a shorstring procedure AppendShortChar(chr: AnsiChar; var dest: ShortString); {$ifdef FPC} inline; {$endif} /// simple concatenation of a byte as hexadecimal into a shorstring procedure AppendShortByteHex(value: byte; var dest: ShortString); /// simple concatenation of a ShortString text into a shorstring procedure AppendShort(const src: ShortString; var dest: ShortString); {$ifdef FPC} inline; {$endif} /// simple concatenation of a #0 ending text into a shorstring // - if Len is < 0, will use StrLen(buf) procedure AppendShortBuffer(buf: PAnsiChar; len: integer; var dest: ShortString); /// simple concatenation of an ANSI-7 AnsiString into a shorstring // - if Len is < 0, will use StrLen(buf) procedure AppendShortAnsi7String(const buf: RawByteString; var dest: ShortString); {$ifdef FPC}inline;{$endif} /// just a wrapper around vmtClassName to avoid a string conversion function ClassNameShort(C: TClass): PShortString; overload; {$ifdef HASINLINE}inline;{$endif} /// just a wrapper around vmtClassName to avoid a string conversion function ClassNameShort(Instance: TObject): PShortString; overload; {$ifdef HASINLINE}inline;{$endif} /// just a wrapper around vmtClassName to avoid a string conversion procedure ClassToText(C: TClass; var result: RawUtf8); /// just a wrapper around ClassToText() to avoid a string conversion function ToText(C: TClass): RawUtf8; overload; {$ifdef HASSAFEINLINE}inline;{$endif} var /// retrieve the unit name where a given class is implemented // - is implemented in mormot.core.rtti.pas; so may be nil otherwise // - is needed since Delphi 7-2009 do not define TObject.UnitName (because // there is no such information available in RTTI) ClassUnit: function(C: TClass): PShortString; /// just a wrapper around vmtParent to avoid a function call // - slightly faster than TClass.ClassParent thanks to proper inlining function GetClassParent(C: TClass): TClass; {$ifdef HASINLINE}inline;{$endif} /// case-insensitive comparison of two shortstrings only containing ASCII 7-bit // - use e.g. with RTTI property names values only including A..Z,0..9,_ chars // - will make the "XOR AND $DF" trick to quickly test A-Z / a-z characters // - behavior is undefined with UTF-8 encoding (some false positive may occur) // - see IdemPropName/IdemPropNameU functions in mormot.core.text for a similar // comparison with other kind of input variables function PropNameEquals(P1, P2: PShortString): boolean; overload; {$ifdef FPC}inline;{$endif} // Delphi has troubles inlining goto/label /// case-insensitive comparison of two RawUtf8 only containing ASCII 7-bit // - use e.g. with RTTI property names values only including A..Z,0..9,_ chars // - will make the "XOR AND $DF" trick to quickly test A-Z / a-z characters // - behavior is undefined with UTF-8 encoding (some false positive may occur) // - see IdemPropName/IdemPropNameU functions in mormot.core.text for a similar // comparison with other kind of input variables function PropNameEquals(const P1, P2: RawUtf8): boolean; overload; /// raw internal method as published by FindNonVoid[false] function FindNonVoidRawUtf8(n: PPointerArray; name: pointer; len: TStrLen; count: PtrInt): PtrInt; /// raw internal method as published by FindNonVoid[true] function FindNonVoidRawUtf8I(n: PPointerArray; name: pointer; len: TStrLen; count: PtrInt): PtrInt; type TFindNonVoid = function(p: PPointerArray; n: pointer; l: TStrLen; c: PtrInt): PtrInt; const /// raw internal methods for case sensitive (or not) search for a RawUtf8 // - expects non-void RawUtf8 values, with ASCII-7 encoding, e.g. as with // TDocVariantData.GetValueIndex() property names FindNonVoid: array[{casesensitive:}boolean] of TFindNonVoid = ( FindNonVoidRawUtf8I, FindNonVoidRawUtf8); /// return the case-insensitive ASCII 7-bit index of Value in non-void Values[] // - typical use with a TRawUtf8DynArray is like this: // ! index := FindPropName(pointer(aDynArray), aValue, length(aDynArray)); // - by design, this function expects Values[] to not contain any void '' function FindPropName(Values: PRawUtf8Array; const Value: RawUtf8; ValuesCount: PtrInt): PtrInt; overload; {$ifdef HASINLINE}inline;{$endif} /// return the index of Value in Values[], -1 if not found // - here name search would use fast IdemPropNameU() function function FindPropName(const Names: array of RawUtf8; const Name: RawUtf8): integer; overload; /// use the RTL to return a date/time as ISO-8601 text // - slow function, here to avoid linking mormot.core.datetime function DateTimeToIsoString(dt: TDateTime): string; /// convert a binary into its human-friendly per-byte hexadecimal lowercase text // - returns e.g. '12:50:b6:1e:c6:aa', i.e. the DN/MAC format // - used e.g. in mormot.lib.openssl11 and mormot.net.sock procedure ToHumanHex(var result: RawUtf8; bin: PByteArray; len: PtrInt); /// convert a binary into its human-friendly hexadecimal in reverse order procedure ToHumanHexReverse(var result: RawUtf8; bin: PByteArray; len: PtrInt); // backward compatibility types redirections {$ifndef PUREMORMOT2} type TSqlRawBlob = RawBlob; {$endif PUREMORMOT2} { ************ Numbers (floats and integers) Low-level Definitions } const /// fast lookup table for converting any decimal number from // 0 to 99 into their ASCII equivalence TwoDigitLookup: packed array[0..99] of array[1..2] of AnsiChar = ('00', '01', '02', '03', '04', '05', '06', '07', '08', '09', '10', '11', '12', '13', '14', '15', '16', '17', '18', '19', '20', '21', '22', '23', '24', '25', '26', '27', '28', '29', '30', '31', '32', '33', '34', '35', '36', '37', '38', '39', '40', '41', '42', '43', '44', '45', '46', '47', '48', '49', '50', '51', '52', '53', '54', '55', '56', '57', '58', '59', '60', '61', '62', '63', '64', '65', '66', '67', '68', '69', '70', '71', '72', '73', '74', '75', '76', '77', '78', '79', '80', '81', '82', '83', '84', '85', '86', '87', '88', '89', '90', '91', '92', '93', '94', '95', '96', '97', '98', '99'); var /// fast lookup table for converting any decimal number from // 0 to 99 into their ASCII equivalence TwoDigitLookupW: packed array[0..99] of word absolute TwoDigitLookup; /// best possible precision when rendering a "single" kind of float // - can be used as parameter for ExtendedToShort/ExtendedToStr // - is defined as a var, so that you may be able to override the default // settings, for the whole process SINGLE_PRECISION: integer = 8; /// best possible precision when rendering a "double" kind of float // - can be used as parameter for ExtendedToShort/ExtendedToStr // - is defined as a var, so that you may be able to override the default // settings, for the whole process DOUBLE_PRECISION: integer = 15; /// best possible precision when rendering a "extended" kind of float // - can be used as parameter for ExtendedToShort/ExtendedToStr // - is defined as a var, so that you may be able to override the default // settings, for the whole process EXTENDED_PRECISION: integer = 18; type /// small structure used as convenient result to Div100() procedure TDiv100Rec = packed record /// contains V div 100 after Div100(V) D: cardinal; /// contains V mod 100 after Div100(V) M: cardinal; end; {$ifdef TSYNEXTENDED80} /// the floating-point type to be used for best precision and speed // - will allow to fallback to double e.g. on x64 and ARM CPUs TSynExtended = extended; TSynExtendedDynArray = array of TSynExtended; PSynExtendedDynArray = ^TSynExtendedDynArray; PSynExtended = ^TSynExtended; {$else} /// ARM/Delphi 64-bit does not support 80bit extended -> double is enough TSynExtended = double; TSynExtendedDynArray = TDoubleDynArray; PSynExtendedDynArray = PDoubleDynArray; PSynExtended = PDouble; {$endif TSYNEXTENDED80} /// the non-number values potentially stored in an IEEE floating point TFloatNan = ( fnNumber, fnNan, fnInf, fnNegInf); {$ifndef FPC_REQUIRES_PROPER_ALIGNMENT} /// unaligned() will be defined and useful only on FPC ARM/Aarch64 plaforms unaligned = Double; {$endif FPC_REQUIRES_PROPER_ALIGNMENT} const /// used e.g. to convert a currency (via PInt64) into a double // - warning: FPC Win64 to Win32 cross-compiler doesn't support currency // values properly -> use FPC Win32 compiler only on Windows CURR_RES = 10000; /// convert a currency value into a double // - using PInt64() division by CURR_RES (=10000) // - warning: FPC Win64 to Win32 cross-compiler doesn't support currency // values properly -> use FPC Win32 compiler only on Windows procedure CurrencyToDouble(const c: currency; out d: double); overload; {$ifdef HASINLINE}inline;{$endif} /// convert a currency value pointer into a double // - using PInt64() division by CURR_RES (=10000) // - warning: FPC Win64 to Win32 cross-compiler doesn't support currency // values properly -> use FPC Win32 compiler only on Windows procedure CurrencyToDouble(c: PCurrency; out d: double); overload; {$ifdef HASINLINE}inline;{$endif} /// convert a currency value pointer into a double // - using PInt64() division by CURR_RES (=10000) // - warning: FPC Win64 to Win32 cross-compiler doesn't support currency // values properly -> use FPC Win32 compiler only on Windows function CurrencyToDouble(c: PCurrency): double; overload; {$ifdef HASINLINE}inline;{$endif} /// fill a variant value from a currency value // - as compatible with VariantToCurrency/VariantToDouble // - warning: FPC Win64 to Win32 cross-compiler doesn't support currency // values properly -> use FPC Win32 compiler only on Windows procedure CurrencyToVariant(const c: currency; var v: variant); {$ifdef HASINLINE}inline;{$endif} /// convert a double value into a currency // - using truncated multiplication by CURR_RES (=10000) // - warning: FPC Win64 to Win32 cross-compiler doesn't support currency // values properly -> use FPC Win32 compiler only on Windows procedure DoubleToCurrency(const d: double; out c: currency); overload; {$ifdef HASINLINE}inline;{$endif} /// convert a double value into a currency // - using truncated multiplication by CURR_RES (=10000) // - warning: FPC Win64 to Win32 cross-compiler doesn't support currency // values properly -> use FPC Win32 compiler only on Windows procedure DoubleToCurrency(const d: double; c: PCurrency); overload; {$ifdef HASINLINE}inline;{$endif} /// convert a double value into a currency // - using truncated multiplication by CURR_RES (=10000) // - warning: FPC Win64 to Win32 cross-compiler doesn't support currency // values properly -> use FPC Win32 compiler only on Windows function DoubleToCurrency(const d: double): currency; overload; {$ifdef HASINLINE}inline;{$endif} /// convert a currency value into a Int64 // - using PInt64() division by CURR_RES (=10000) // - warning: FPC Win64 to Win32 cross-compiler doesn't support currency // values properly -> use FPC Win32 compiler only on Windows procedure CurrencyToInt64(c: PCurrency; var i: Int64); overload; {$ifdef HASINLINE}inline;{$endif} /// convert a Int64 value into a currency // - using multiplication by CURR_RES (=10000) // - warning: FPC Win64 to Win32 cross-compiler doesn't support currency // values properly -> use FPC Win32 compiler only on Windows procedure Int64ToCurrency(const i: Int64; out c: currency); overload; {$ifdef HASINLINE}inline;{$endif} /// convert a Int64 value into a currency // - using multiplication by CURR_RES (=10000) // - warning: FPC Win64 to Win32 cross-compiler doesn't support currency // values properly -> use FPC Win32 compiler only on Windows procedure Int64ToCurrency(const i: Int64; c: PCurrency); overload; {$ifdef HASINLINE}inline;{$endif} /// no banker rounding into two digits after the decimal point // - #.##51 will round to #.##+0.01 and #.##50 will be truncated to #.## // - implementation will use fast Int64 math to avoid any precision loss due to // temporary floating-point conversion function SimpleRoundTo2Digits(Value: Currency): Currency; {$ifdef HASINLINE}inline;{$endif} /// simple, no banker rounding of a Currency value, stored as Int64, to only 2 digits // - #.##51 will round to #.##+0.01 and #.##50 will be truncated to #.## // - implementation will use fast Int64 math to avoid any precision loss due to // temporary floating-point conversion procedure SimpleRoundTo2DigitsCurr64(var Value: Int64); /// no banker rounding into text, with two digits after the decimal point // - #.##51 will round to #.##+0.01 and #.##50 will be truncated to #.## // - this function will only allow 2 digits in the returned text function TwoDigits(const d: double): TShort31; /// truncate a currency value to only 2 digits // - implementation will use fast Int64 math to avoid any precision loss due to // temporary floating-point conversion function TruncTo2Digits(Value: currency): currency; /// truncate a currency value, stored as Int64, to only 2 digits // - implementation will use fast Int64 math to avoid any precision loss due to // temporary floating-point conversion procedure TruncTo2DigitsCurr64(var Value: Int64); {$ifdef HASINLINE}inline;{$endif} /// truncate a Currency value, stored as Int64, to only 2 digits // - implementation will use fast Int64 math to avoid any precision loss due to // temporary floating-point conversion function TruncTo2Digits64(Value: Int64): Int64; {$ifdef HASINLINE}inline;{$endif} /// simple wrapper to efficiently compute both division and modulo per 100 // - compute result.D = Y div 100 and result.M = Y mod 100 // - under FPC, will use fast multiplication by reciprocal so can be inlined // - under Delphi, we use our own optimized asm version (which can't be inlined) procedure Div100(Y: cardinal; var res: TDiv100Rec); {$ifdef FPC} inline; {$endif} /// get the signed 32-bit integer value stored in P^ // - we use the PtrInt result type, even if expected to be 32-bit, to use // native CPU register size (don't want any 32-bit overflow here) // - will end parsing when P^ does not contain any number (e.g. it reaches any // ending #0 char) function GetInteger(P: PUtf8Char): PtrInt; overload; /// get the signed 32-bit integer value stored in P^..PEnd^ // - will end parsing when P^ does not contain any number (e.g. it reaches any // ending #0 char), or when P reached PEnd (avoiding any buffer overflow) function GetInteger(P, PEnd: PUtf8Char): PtrInt; overload; /// get the signed 32-bit integer value stored in P^ // - if P if nil or not start with a valid numerical value, returns Default function GetIntegerDef(P: PUtf8Char; Default: PtrInt): PtrInt; {$ifdef HASINLINE}inline;{$endif} /// get the signed 32-bit integer value stored in P^ // - this version return 0 in err if no error occurred, and 1 if an invalid // character was found, not its exact index as for the val() function function GetInteger(P: PUtf8Char; var err: integer): PtrInt; overload; /// get the unsigned 32-bit integer value stored in P^ // - we use the PtrUInt result type, even if expected to be 32-bit, to use // native CPU register size (don't want any 32-bit overflow here) function GetCardinal(P: PUtf8Char): PtrUInt; overload; /// get the unsigned 32-bit integer value stored in P^ // - we use the PtrUInt result type, even if expected to be 32-bit, to use // native CPU register size (don't want any 32-bit overflow here) function GetCardinal(P, PEnd: PUtf8Char): PtrUInt; overload; /// get the unsigned 32-bit integer value stored in P^ // - if P if nil or not start with a valid numerical value, returns Default function GetCardinalDef(P: PUtf8Char; Default: PtrUInt): PtrUInt; /// get the unsigned 32-bit integer value stored as Unicode string in P^ function GetCardinalW(P: PWideChar): PtrUInt; /// get a boolean value stored as 'true'/'false' text in P^ // - would also recognize any non '0' integer as true, or false if P = nil // - see relaxed GetInt64Bool() to recognize e.g. 'TRUE' or 'yes'/'YES' function GetBoolean(P: PUtf8Char): boolean; overload; {$ifdef HASINLINE}inline;{$endif} /// get a boolean value stored as 'true'/'false' text in input variable // - would also recognize any non '0' integer as true, or false if P is '' function GetBoolean(const value: RawUtf8): boolean; overload; {$ifdef HASINLINE}inline;{$endif} /// get the 64-bit integer value stored in P^ function GetInt64(P: PUtf8Char): Int64; overload; {$ifdef HASINLINE}inline;{$endif} /// get the 64-bit integer value stored in P^ // - if P if nil or not start with a valid numerical value, returns Default function GetInt64Def(P: PUtf8Char; const Default: Int64): Int64; /// return 1 if 'TRUE' or 'YES', or 0 otherwise function GetTrue(P: PUtf8Char): integer; {$ifdef HASINLINE}inline;{$endif} /// get the 64-bit integer value from P^, recognizing true/false/yes/no input // - return true on correct parsing, false if P is no number or boolean function GetInt64Bool(P: PUtf8Char; out V: Int64): boolean; /// get the 64-bit signed integer value stored in P^ procedure SetInt64(P: PUtf8Char; var result: Int64); {$ifdef CPU64}inline;{$endif} /// get the 64-bit unsigned integer value stored in P^ procedure SetQWord(P: PUtf8Char; var result: QWord); overload; {$ifdef CPU64}inline;{$endif} /// get the 64-bit unsigned integer value stored in P^ procedure SetQWord(P, PEnd: PUtf8Char; var result: QWord); overload; {$ifdef CPU64}inline;{$endif} /// get the 64-bit signed integer value stored in P^ // - set the err content to the index of any faulty character, 0 if conversion // was successful (same as the standard val function) function GetInt64(P: PUtf8Char; var err: integer): Int64; overload; {$ifdef CPU64}inline;{$endif} /// get the 64-bit unsigned integer value stored in P^ // - set the err content to the index of any faulty character, 0 if conversion // was successful (same as the standard val function) function GetQWord(P: PUtf8Char; var err: integer): QWord; /// get the extended floating point value stored in P^ // - set the err content to the index of any faulty character, 0 if conversion // was successful (same as the standard val function) // - this optimized function is consistent on all platforms/compilers and return // the decoded value even if err is not 0 (e.g. if P^ is not #0 ended) function GetExtended(P: PUtf8Char; out err: integer): TSynExtended; overload; /// get the extended floating point value stored in P^ // - this overloaded version returns 0 as a result if the content of P is invalid function GetExtended(P: PUtf8Char): TSynExtended; overload; {$ifdef HASINLINE}inline;{$endif} type TPow10 = array[-31..55] of TSynExtended; PPow10 = ^TPow10; const /// most common 10 ^ exponent constants, ending with values for HugePower10*() POW10: TPow10 = ( 1E-31, 1E-30, 1E-29, 1E-28, 1E-27, 1E-26, 1E-25, 1E-24, 1E-23, 1E-22, 1E-21, 1E-20, 1E-19, 1E-18, 1E-17, 1E-16, 1E-15, 1E-14, 1E-13, 1E-12, 1E-11, 1E-10, 1E-9, 1E-8, 1E-7, 1E-6, 1E-5, 1E-4, 1E-3, 1E-2, 1E-1, 1E0, 1E1, 1E2, 1E3, 1E4, 1E5, 1E6, 1E7, 1E8, 1E9, 1E10, 1E11, 1E12, 1E13, 1E14, 1E15, 1E16, 1E17, 1E18, 1E19, 1E20, 1E21, 1E22, 1E23, 1E24, 1E25, 1E26, 1E27, 1E28, 1E29, 1E30, 1E31, 0,{32} -1,{33} 1E0,{34} 1E32, 1E64, 1E96, 1E128, 1E160, 1E192, 1E224, 1E256, 1E288, 1E320, 1E-0,{45} 1E-32, 1E-64, 1E-96, 1E-128, 1E-160, 1E-192, 1E-224, 1E-256, 1E-288, 1E-320); /// low-level computation of 10 ^ positive exponent, if POW10[] is not enough function HugePower10Pos(exponent: PtrInt; pow10: PPow10): TSynExtended; {$ifdef HASINLINE}inline;{$endif} /// low-level computation of 10 ^ negative exponent, if POW10[] is not enough function HugePower10Neg(exponent: PtrInt; pow10: PPow10): TSynExtended; {$ifdef HASINLINE}inline;{$endif} /// get the signed 32-bit integer value stored in a RawUtf8 string // - we use the PtrInt result type, even if expected to be 32-bit, to use // native CPU register size (don't want any 32-bit overflow here) function Utf8ToInteger(const value: RawUtf8; Default: PtrInt = 0): PtrInt; overload; {$ifdef HASINLINE}inline;{$endif} /// get the signed 64-bit integer value stored in a RawUtf8 string // - returns the default value if the supplied text was not successfully // converted into an Int64 function Utf8ToInt64(const text: RawUtf8; const default: Int64 = 0): Int64; /// get and check range of a signed 32-bit integer stored in a RawUtf8 string // - we use the PtrInt result type, even if expected to be 32-bit, to use // native CPU register size (don't want any 32-bit overflow here) function Utf8ToInteger(const value: RawUtf8; min, max: PtrInt; default: PtrInt = 0): PtrInt; overload; {$ifdef HASINLINE}inline;{$endif} /// get the signed 32-bit integer value stored in a RawUtf8 string // - returns TRUE if the supplied text was successfully converted into an integer function ToInteger(const text: RawUtf8; out value: integer): boolean; {$ifdef HASINLINE}inline;{$endif} /// get the unsigned 32-bit cardinal value stored in a RawUtf8 string // - returns TRUE if the supplied text was successfully converted into a cardinal function ToCardinal(const text: RawUtf8; out value: cardinal; minimal: cardinal = 0): boolean; {$ifdef HASINLINE}inline;{$endif} /// get the signed 64-bit integer value stored in a RawUtf8 string // - returns TRUE if the supplied text was successfully converted into an Int64 function ToInt64(const text: RawUtf8; out value: Int64): boolean; {$ifdef HASINLINE}inline;{$endif} /// get a 64-bit floating-point value stored in a RawUtf8 string // - returns TRUE if the supplied text was successfully converted into a double function ToDouble(const text: RawUtf8; out value: double): boolean; {$ifdef HASINLINE}inline;{$endif} /// internal fast integer val to text conversion // - expect the last available temporary char position in P // - return the last written char position (write in reverse order in P^) // - typical use: // !function Int32ToUtf8(Value: PtrInt): RawUtf8; // !var // ! tmp: array[0..23] of AnsiChar; // ! P: PAnsiChar; // !begin // ! P := StrInt32(@tmp[23],Value); // ! SetString(result,P,@tmp[23]-P); // !end; // - convert the input value as PtrInt, so as Int64 on 64-bit CPUs // - not to be called directly: use IntToStr() or Int32ToUtf8() instead function StrInt32(P: PAnsiChar; val: PtrInt): PAnsiChar; /// internal fast unsigned integer val to text conversion // - expect the last available temporary char position in P // - return the last written char position (write in reverse order in P^) // - convert the input value as PtrUInt, so as QWord on 64-bit CPUs function StrUInt32(P: PAnsiChar; val: PtrUInt): PAnsiChar; /// internal fast Int64 val to text conversion // - same calling convention as with StrInt32() above function StrInt64(P: PAnsiChar; const val: Int64): PAnsiChar; {$ifdef HASINLINE}inline;{$endif} /// internal fast unsigned Int64 val to text conversion // - same calling convention as with StrInt32() above function StrUInt64(P: PAnsiChar; const val: QWord): PAnsiChar; {$ifdef CPU64}inline;{$endif} /// add the 4 digits of integer Y to P^ as '0000'..'9999' procedure YearToPChar(Y: PtrUInt; P: PUtf8Char); {$ifndef ASMX86} {$ifdef HASINLINE}inline;{$endif} {$endif} const /// a typical error allowed when working with double floating-point values // - 1E-12 is too small, and triggers sometimes some unexpected errors; // FPC RTL uses 1E-4 so we are paranoid enough DOUBLE_SAME = 1E-11; /// compare to floating point values, with IEEE 754 double precision // - use this function instead of raw = operator // - the precision is calculated from the A and B value range // - faster equivalent than SameValue() in Math unit // - if you know the precision range of A and B, it's faster to check abs(A-B)QWord(B) is wrong on older versions of Delphi, so you // should better use this function or SortDynArrayQWord() to properly compare // two QWord values over CPUX86 on Delphi 7-2007 function CompareQWord(const A, B: QWord): integer; {$ifdef HASINLINE}inline;{$endif} /// fast search of an unsigned integer item in a 32-bit integer array // - Count is the number of cardinal entries in P^ // - returns P where P^=Value // - returns nil if Value was not found // - is implemented via IntegerScanIndex() SSE2 asm on i386 and x86_64 function IntegerScan(P: PCardinalArray; Count: PtrInt; Value: cardinal): PCardinal; {$ifdef CPUINTEL} {$ifndef HASNOSSE2} {$ifdef HASINLINE}inline;{$endif} {$endif} {$endif} /// fast search of an unsigned integer position in a 32-bit integer array // - Count is the number of integer entries in P^ // - return index of P^[index]=Value // - return -1 if Value was not found // - is implemented with SSE2 asm on i386 and x86_64 function IntegerScanIndex(P: PCardinalArray; Count: PtrInt; Value: cardinal): PtrInt; {$ifndef CPUINTEL}inline;{$endif} /// fast search of an unsigned integer in a 32-bit integer array // - returns true if P^=Value within Count entries // - returns false if Value was not found // - is implemented via IntegerScanIndex() SSE2 asm on i386 and x86_64 function IntegerScanExists(P: PCardinalArray; Count: PtrInt; Value: cardinal): boolean; {$ifdef CPUINTEL} {$ifndef HASNOSSE2} {$ifdef HASINLINE}inline;{$endif} {$endif} {$endif} /// fast search of an integer position in a 64-bit integer array // - Count is the number of Int64 entries in P^ // - returns P where P^=Value // - returns nil if Value was not found function Int64Scan(P: PInt64Array; Count: PtrInt; const Value: Int64): PInt64; /// fast search of an integer position in a signed 64-bit integer array // - Count is the number of Int64 entries in P^ // - returns index of P^[index]=Value // - returns -1 if Value was not found function Int64ScanIndex(P: PInt64Array; Count: PtrInt; const Value: Int64): PtrInt; {$ifdef HASSAFEINLINE}inline;{$endif} /// fast search of an integer position in an unsigned 64-bit integer array // - Count is the number of QWord entries in P^ // - returns index of P^[index]=Value // - returns -1 if Value was not found function QWordScanIndex(P: PQWordArray; Count: PtrInt; const Value: QWord): PtrInt; {$ifdef HASINLINE}inline;{$endif} /// fast search of an integer value in a 64-bit integer array // - returns true if P^=Value within Count entries // - returns false if Value was not found function Int64ScanExists(P: PInt64Array; Count: PtrInt; const Value: Int64): boolean; /// fast search of a pointer-sized unsigned integer position // in an pointer-sized integer array // - Count is the number of pointer-sized integer entries in P^ // - return index of P^[index]=Value // - return -1 if Value was not found function PtrUIntScanIndex(P: PPtrUIntArray; Count: PtrInt; Value: PtrUInt): PtrInt; {$ifdef HASINLINE}inline;{$endif} /// fast search of a pointer-sized unsigned integer in an pointer-sized integer array // - Count is the number of pointer-sized integer entries in P^ // - returns true if P^=Value within Count entries // - returns false if Value was not found function PtrUIntScan(P: PPtrUIntArray; Count: PtrInt; Value: PtrUInt): pointer; {$ifdef HASINLINE}inline;{$endif} /// fast search of a pointer-sized unsigned integer position // in an pointer-sized integer array // - Count is the number of pointer-sized integer entries in P^ // - returns true if P^=Value within Count entries // - returns false if Value was not found function PtrUIntScanExists(P: PPtrUIntArray; Count: PtrInt; Value: PtrUInt): boolean; {$ifdef HASINLINE}inline;{$endif} /// fast search of an unsigned byte value position in a byte array // - Count is the number of byte entries in P^ // - return index of P^[index]=Value, -1 if Value was not found // - is implemented with SSE2 asm on i386 and x86_64 function ByteScanIndex(P: PByteArray; Count: PtrInt; Value: byte): PtrInt; {$ifndef CPUINTEL} inline; {$endif} /// fast search of an unsigned Word value position in a Word array // - Count is the number of Word entries in P^ // - return index of P^[index]=Value, -1 if Value was not found // - is implemented with SSE2 asm on i386 and x86_64 function WordScanIndex(P: PWordArray; Count: PtrInt; Value: word): PtrInt; {$ifndef CPUINTEL} inline; {$endif} /// sort an integer array, low values first procedure QuickSortInteger(ID: PIntegerArray; L, R: PtrInt); overload; /// sort an integer array, low values first procedure QuickSortInteger(ID, CoValues: PIntegerArray; L, R: PtrInt); overload; /// sort an integer array, low values first procedure QuickSortInteger(var ID: TIntegerDynArray); overload; /// sort a 16-bit unsigned integer array, low values first procedure QuickSortWord(ID: PWordArray; L, R: PtrInt); /// sort a 64-bit signed integer array, low values first procedure QuickSortInt64(ID: PInt64Array; L, R: PtrInt); overload; /// sort a 64-bit unsigned integer array, low values first // - QWord comparison are implemented correctly under FPC or Delphi 2009+ - // older compilers will use fast and exact SortDynArrayQWord() procedure QuickSortQWord(ID: PQWordArray; L, R: PtrInt); overload; /// sort a 64-bit integer array, low values first procedure QuickSortInt64(ID, CoValues: PInt64Array; L, R: PtrInt); overload; /// sort a PtrInt array, low values first procedure QuickSortPtrInt(P: PPtrIntArray; L, R: PtrInt); {$ifdef HASINLINE}inline;{$endif} /// sort a pointer array, low values first procedure QuickSortPointer(P: PPointerArray; L, R: PtrInt); {$ifdef HASINLINE}inline;{$endif} /// sort a double array, low values first procedure QuickSortDouble(ID: PDoubleArray; L, R: PtrInt); /// fast O(log(n)) binary search of an integer value in a sorted integer array // - R is the last index of available integer entries in P^ (i.e. Count-1) // - return index of P^[result]=Value // - return -1 if Value was not found // - use branchless asm on x86_64 function FastFindIntegerSorted(P: PIntegerArray; R: PtrInt; Value: integer): PtrInt; overload; /// fast O(log(n)) binary search of an integer value in a sorted integer array // - return index of Values[result]=Value // - return -1 if Value was not found function FastFindIntegerSorted(const Values: TIntegerDynArray; Value: integer): PtrInt; overload; {$ifdef HASINLINE}inline;{$endif} /// fast O(log(n)) binary search of a 16-bit unsigned integer value in a sorted array // - use branchless asm on x86_64 function FastFindWordSorted(P: PWordArray; R: PtrInt; Value: Word): PtrInt; /// fast O(log(n)) binary search of a 64-bit signed integer value in a sorted array // - R is the last index of available integer entries in P^ (i.e. Count-1) // - return index of P^[result]=Value // - return -1 if Value was not found // - use branchless asm on x86_64 function FastFindInt64Sorted(P: PInt64Array; R: PtrInt; const Value: Int64): PtrInt; overload; /// fast O(log(n)) binary search of a 64-bit unsigned integer value in a sorted array // - R is the last index of available integer entries in P^ (i.e. Count-1) // - return index of P^[result]=Value // - return -1 if Value was not found // - QWord comparison are implemented correctly under FPC or Delphi 2009+ - // older compilers will fast and exact SortDynArrayQWord() function FastFindQWordSorted(P: PQWordArray; R: PtrInt; const Value: QWord): PtrInt; overload; /// fast O(log(n)) binary search of a PtrInt value in a sorted array function FastFindPtrIntSorted(P: PPtrIntArray; R: PtrInt; Value: PtrInt): PtrInt; overload; {$ifdef HASINLINE}inline;{$endif} /// fast O(log(n)) binary search of a Pointer value in a sorted array function FastFindPointerSorted(P: PPointerArray; R: PtrInt; Value: Pointer): PtrInt; overload; {$ifdef HASINLINE}inline;{$endif} /// retrieve the index where to insert an integer value in a sorted integer array // - R is the last index of available integer entries in P^ (i.e. Count-1) // - returns -(foundindex+1) i.e. <0 if the specified Value was found function FastLocateIntegerSorted(P: PIntegerArray; R: PtrInt; Value: integer): PtrInt; /// retrieve the matching index or where to insert an integer value function FastSearchIntegerSorted(P: PIntegerArray; R: PtrInt; Value: integer): PtrInt; /// retrieve the index where to insert a word value in a sorted word array // - R is the last index of available integer entries in P^ (i.e. Count-1) // - returns -(foundindex+1) i.e. <0 if the specified Value was found function FastLocateWordSorted(P: PWordArray; R: integer; Value: word): PtrInt; /// add an integer value in a sorted dynamic array of integers // - returns the index where the Value was added successfully in Values[] // - returns -(foundindex+1) i.e. <0 if the specified Value was already present // - if CoValues is set, its content will be moved to allow inserting a new // value at CoValues[result] position function AddSortedInteger(var Values: TIntegerDynArray; var ValuesCount: integer; Value: integer; CoValues: PIntegerDynArray = nil): PtrInt; overload; /// add an integer value in a sorted dynamic array of integers // - overloaded function which do not expect an external Count variable function AddSortedInteger(var Values: TIntegerDynArray; Value: integer; CoValues: PIntegerDynArray = nil): PtrInt; overload; /// insert an integer value at the specified index position of a dynamic array // of integers // - if Index is invalid, the Value is inserted at the end of the array function InsertInteger(var Values: TIntegerDynArray; var ValuesCount: integer; Value: integer; Index: PtrInt; CoValues: PIntegerDynArray = nil): PtrInt; /// add an integer value at the end of a dynamic array of integers // - returns TRUE if Value was added successfully in Values[], in this case // length(Values) will be increased function AddInteger(var Values: TIntegerDynArray; Value: integer; NoDuplicates: boolean = false): boolean; overload; /// add an integer value at the end of a dynamic array of integers // - this overloaded function will use a separate Count variable (faster) // - it won't search for any existing duplicate procedure AddInteger(var Values: TIntegerDynArray; var ValuesCount: integer; Value: integer); overload; {$ifdef HASINLINE}inline;{$endif} /// add an integer array at the end of a dynamic array of integer function AddInteger(var Values: TIntegerDynArray; const Another: TIntegerDynArray): PtrInt; overload; /// add an integer value at the end of a dynamic array of integers // - this overloaded function will use a separate Count variable (faster), // and would allow to search for duplicates // - returns TRUE if Value was added successfully in Values[], in this case // ValuesCount will be increased, but length(Values) would stay fixed most // of the time (since it stores the Values[] array capacity) function AddInteger(var Values: TIntegerDynArray; var ValuesCount: integer; Value: integer; NoDuplicates: boolean): boolean; overload; /// add a 16-bit integer value at the end of a dynamic array of integers function AddWord(var Values: TWordDynArray; var ValuesCount: integer; Value: Word): PtrInt; /// add a 64-bit integer value at the end of a dynamic array of integers function AddInt64(var Values: TInt64DynArray; var ValuesCount: integer; Value: Int64): PtrInt; overload; {$ifdef HASINLINE}inline;{$endif} /// add a 64-bit integer value at the end of a dynamic array function AddInt64(var Values: TInt64DynArray; Value: Int64): PtrInt; overload; {$ifdef HASINLINE}inline;{$endif} /// add a 64-bit integer array at the end of a dynamic array function AddInt64(var Values: TInt64DynArray; const Another: TInt64DynArray): PtrInt; overload; /// if not already existing, add a 64-bit integer value to a dynamic array function AddInt64Once(var Values: TInt64DynArray; Value: Int64): PtrInt; /// if not already existing, add a 64-bit integer value to a sorted dynamic array procedure AddInt64Sorted(var Values: TInt64DynArray; Value: Int64); /// add a pointer-sized integer array at the end of a dynamic array function AddPtrUInt(var Values: TPtrUIntDynArray; var ValuesCount: integer; Value: PtrUInt): PtrInt; /// delete any 32-bit integer in Values[] procedure DeleteInteger(var Values: TIntegerDynArray; Index: PtrInt); overload; /// delete any 32-bit integer in Values[] procedure DeleteInteger(var Values: TIntegerDynArray; var ValuesCount: integer; Index: PtrInt); overload; /// delete any 16-bit integer in Values[] procedure DeleteWord(var Values: TWordDynArray; Index: PtrInt); /// delete any 64-bit integer in Values[] procedure DeleteInt64(var Values: TInt64DynArray; Index: PtrInt); overload; /// delete any 64-bit integer in Values[] procedure DeleteInt64(var Values: TInt64DynArray; var ValuesCount: integer; Index: PtrInt); overload; /// fill some values with i,i+1,i+2...i+Count-1 procedure FillIncreasing(Values: PIntegerArray; StartValue: integer; Count: PtrUInt); /// quick helper to initialize a dynamic array of integer from some constants // - can be used e.g. as: // ! MyArray := TIntegerDynArrayFrom([1,2,3]); // - see also FromI32() function TIntegerDynArrayFrom(const Values: array of integer): TIntegerDynArray; /// quick helper to initialize a dynamic array of integer from 64-bit integers // - will raise an Exception if any Value[] can not fit into 32-bit, unless // raiseExceptionOnOverflow is FALSE and the returned array slot is filled // with maxInt/minInt function TIntegerDynArrayFrom64(const Values: TInt64DynArray; raiseExceptionOnOverflow: boolean = true): TIntegerDynArray; /// quick helper to initialize a dynamic array of 64-bit integers from 32-bit values // - see also FromI64() for 64-bit signed integer values input function TInt64DynArrayFrom(const Values: TIntegerDynArray): TInt64DynArray; /// quick helper to initialize a dynamic array of 64-bit integers from 32-bit values // - see also FromU64() for 64-bit unsigned integer values input function TQWordDynArrayFrom(const Values: TCardinalDynArray): TQWordDynArray; /// initializes a dynamic array from a set of 32-bit integer signed values function FromI32(const Values: array of integer): TIntegerDynArray; {$ifdef FPC}{$ifdef HASINLINE}inline;{$endif}{$endif} /// initializes a dynamic array from a set of 32-bit integer unsigned values function FromU32(const Values: array of cardinal): TCardinalDynArray; {$ifdef FPC}{$ifdef HASINLINE}inline;{$endif}{$endif} /// initializes a dynamic array from a set of 64-bit integer signed values function FromI64(const Values: array of Int64): TInt64DynArray; {$ifdef FPC}{$ifdef HASINLINE}inline;{$endif}{$endif} /// initializes a dynamic array from a set of 64-bit integer unsigned values function FromU64(const Values: array of QWord): TQWordDynArray; {$ifdef FPC}{$ifdef HASINLINE}inline;{$endif}{$endif} type /// used to store and retrieve Words in a sorted array // - ensure Count=0 before use - if not defined as a private member of a class {$ifdef USERECORDWITHMETHODS} TSortedWordArray = record {$else} TSortedWordArray = object {$endif USERECORDWITHMETHODS} public /// the actual 16-bit word storage Values: TWordDynArray; /// how many items are currently in Values[] Count: PtrInt; /// add a value into the sorted array // - return the index of the new inserted value into the Values[] array // - return -(foundindex+1) if this value is already in the Values[] array function Add(aValue: Word): PtrInt; /// return the index if the supplied value in the Values[] array // - return -1 if not found function IndexOf(aValue: Word): PtrInt; {$ifdef HASINLINE}inline;{$endif} /// save the internal array into a TWordDynArray variable procedure SetArray(out aValues: TWordDynArray); end; PSortedWordArray = ^TSortedWordArray; /// used to store and retrieve Integers in a sorted array // - ensure Count=0 before use - if not defined as a private member of a class {$ifdef USERECORDWITHMETHODS} TSortedIntegerArray = record {$else} TSortedIntegerArray = object {$endif USERECORDWITHMETHODS} public /// the actual 32-bit integers storage Values: TIntegerDynArray; /// how many items are currently in Values[] Count: PtrInt; /// add a value into the sorted array // - return the index of the new inserted value into the Values[] array // - return -(foundindex+1) if this value is already in the Values[] array function Add(aValue: integer): PtrInt; /// return the index if the supplied value in the Values[] array // - return -1 if not found function IndexOf(aValue: integer): PtrInt; {$ifdef HASINLINE}inline;{$endif} /// save the internal array into a TWordDynArray variable procedure SetArray(out aValues: TIntegerDynArray); end; PSortedIntegerArray = ^TSortedIntegerArray; /// compute GCD of two integers using modulo-based Euclidean algorithm function gcd(a, b: PtrUInt): PtrUInt; { ************ ObjArray PtrArray InterfaceArray Wrapper Functions } /// wrapper to add an item to a array of pointer dynamic array storage function PtrArrayAdd(var aPtrArray; aItem: pointer): integer; overload; {$ifdef HASINLINE}inline;{$endif} /// wrapper to add an item to a array of pointer dynamic array storage function PtrArrayAdd(var aPtrArray; aItem: pointer; var aPtrArrayCount: integer): PtrInt; overload; /// wrapper to add once an item to a array of pointer dynamic array storage function PtrArrayAddOnce(var aPtrArray; aItem: pointer): PtrInt; overload; /// wrapper to add once an item to a array of pointer dynamic array storage function PtrArrayAddOnce(var aPtrArray; aItem: pointer; var aPtrArrayCount: integer): PtrInt; overload; /// wrapper to insert an item to a array of pointer dynamic array storage function PtrArrayInsert(var aPtrArray; aItem: pointer; aIndex: PtrInt; var aPtrArrayCount: integer): PtrInt; overload; /// wrapper to delete an item from a array of pointer dynamic array storage function PtrArrayDelete(var aPtrArray; aItem: pointer; aCount: PInteger = nil): PtrInt; overload; /// wrapper to delete an item from a array of pointer dynamic array storage procedure PtrArrayDelete(var aPtrArray; aIndex: PtrInt; aCount: PInteger = nil); overload; /// wrapper to find an item to a array of pointer dynamic array storage function PtrArrayFind(var aPtrArray; aItem: pointer): integer; {$ifdef HASINLINE}inline;{$endif} /// wrapper to add an item to a T*ObjArray dynamic array storage // - for proper serialization on Delphi 7-2009, use Rtti.RegisterObjArray() // - could be used as such (note the T*ObjArray type naming convention): // ! TUserObjArray = array of TUser; // ! ... // ! var arr: TUserObjArray; // ! user: TUser; // ! .. // ! try // ! user := TUser.Create; // ! user.Name := 'Name'; // ! index := ObjArrayAdd(arr,user); // ! ... // ! finally // ! ObjArrayClear(arr); // release all items // ! end; // - return the index of the item in the dynamic array function ObjArrayAdd(var aObjArray; aItem: TObject): PtrInt; overload; {$ifdef HASINLINE}inline;{$endif} /// wrapper to add an item to a T*ObjArray dynamic array storage // - this overloaded function will use a separated variable to store the items // count, so will be slightly faster: but you should call SetLength() when done, // to have a stand-alone array as expected by our ORM/SOA serialziation // - return the index of the item in the dynamic array function ObjArrayAddCount(var aObjArray; aItem: TObject; var aObjArrayCount: integer): PtrInt; {$ifdef HASINLINE}inline;{$endif} /// wrapper to add items to a T*ObjArray dynamic array storage // - aSourceObjArray[] items are just copied to aDestObjArray, which remains untouched // - return the new number of the items in aDestObjArray function ObjArrayAddFrom(var aDestObjArray; const aSourceObjArray): PtrInt; /// wrapper to add and move items to a T*ObjArray dynamic array storage // - aSourceObjArray[] items will be owned by aDestObjArray[], therefore // aSourceObjArray is set to nil // - return the new number of the items in aDestObjArray function ObjArrayAppend(var aDestObjArray, aSourceObjArray): PtrInt; /// wrapper to add once an item to a T*ObjArray dynamic array storage // - for proper serialization on Delphi 7-2009, use Rtti.RegisterObjArray() // - if the object is already in the array (searching by address/reference, // not by content), return its current index in the dynamic array // - if the object does not appear in the array, add it at the end function ObjArrayAddOnce(var aObjArray; aItem: TObject): PtrInt; overload; {$ifdef HASINLINE}inline;{$endif} /// wrapper to add once an item to a T*ObjArray dynamic array storage and Count function ObjArrayAddOnce(var aObjArray; aItem: TObject; var aObjArrayCount: integer): PtrInt; overload; {$ifdef HASINLINE}inline;{$endif} // - aSourceObjArray[] items are just copied to aDestObjArray, which remains untouched // - will first check if aSourceObjArray[] items are not already in aDestObjArray // - return the new number of the items in aDestObjArray function ObjArrayAddOnceFrom(var aDestObjArray; const aSourceObjArray): PtrInt; /// wrapper to set the length of a T*ObjArray dynamic array storage // - could be used as an alternative to SetLength() when you do not // know the exact T*ObjArray type procedure ObjArraySetLength(var aObjArray; aLength: integer); {$ifdef HASINLINE}inline;{$endif} /// wrapper to search an item in a T*ObjArray dynamic array storage // - for proper serialization on Delphi 7-2009, use Rtti.RegisterObjArray() // - search is performed by address/reference, not by content // - returns -1 if the item is not found in the dynamic array function ObjArrayFind(const aObjArray; aItem: TObject): PtrInt; overload; {$ifdef HASINLINE}inline;{$endif} /// wrapper to search an item in a T*ObjArray dynamic array storage // - for proper serialization on Delphi 7-2009, use Rtti.RegisterObjArray() // - search is performed by address/reference, not by content // - returns -1 if the item is not found in the dynamic array function ObjArrayFind(const aObjArray; aCount: integer; aItem: TObject): PtrInt; overload; {$ifdef HASINLINE}inline;{$endif} /// wrapper to count all not nil items in a T*ObjArray dynamic array storage // - for proper serialization on Delphi 7-2009, use Rtti.RegisterObjArray() function ObjArrayNotNilCount(const aObjArray): integer; /// wrapper to delete an item in a T*ObjArray dynamic array storage // - for proper serialization on Delphi 7-2009, use Rtti.RegisterObjArray() // - do nothing if the index is out of range in the dynamic array procedure ObjArrayDelete(var aObjArray; aItemIndex: PtrInt; aContinueOnException: boolean = false; aCount: PInteger = nil); overload; /// wrapper to delete an item in a T*ObjArray dynamic array storage // - for proper serialization on Delphi 7-2009, use Rtti.RegisterObjArray() // - search is performed by address/reference, not by content // - do nothing if the item is not found in the dynamic array function ObjArrayDelete(var aObjArray; aItem: TObject): PtrInt; overload; /// wrapper to delete an item in a T*ObjArray dynamic array storage // - for proper serialization on Delphi 7-2009, use Rtti.RegisterObjArray() // - search is performed by address/reference, not by content // - do nothing if the item is not found in the dynamic array function ObjArrayDelete(var aObjArray; aCount: integer; aItem: TObject): PtrInt; overload; /// wrapper to release all items stored in a T*ObjArray dynamic array // - for proper serialization on Delphi 7-2009, use Rtti.RegisterObjArray() // - you should always use ObjArrayClear() before the array storage is released, // e.g. in the owner class destructor // - when T*ObjArray are used as SOA parameters, no need to release the values // - will also set the dynamic array length to 0, so could be used to re-use // an existing T*ObjArray procedure ObjArrayClear(var aObjArray); overload; /// wrapper to release all items stored in a T*ObjArray dynamic array // - this overloaded function will use the supplied array length as parameter // - you should always use ObjArrayClear() before the array storage is released, // e.g. in the owner class destructor // - will also set the dynamic array length to 0, so could be used to re-use // an existing T*ObjArray procedure ObjArrayClear(var aObjArray; aCount: integer); overload; /// wrapper to release all items stored in a T*ObjArray dynamic array // - for proper serialization on Delphi 7-2009, use Rtti.RegisterObjArray() // - you should always use ObjArrayClear() before the array storage is released, // e.g. in the owner class destructor // - will also set the dynamic array length to 0, so could be used to re-use // an existing T*ObjArray procedure ObjArrayClear(var aObjArray; aContinueOnException: boolean; aCount: PInteger = nil); overload; /// wrapper to release all items stored in an array of T*ObjArray dynamic array // - e.g. aObjArray may be defined as "array of array of TSynFilter" procedure ObjArrayObjArrayClear(var aObjArray); /// wrapper to release all items stored in several T*ObjArray dynamic arrays // - for proper serialization on Delphi 7-2009, use Rtti.RegisterObjArray() procedure ObjArraysClear(const aObjArray: array of pointer); /// low-level function calling FreeAndNil(o^) successively n times procedure RawObjectsClear(o: PObject; n: integer); /// same as FreeAndNil() but catching and ignoring any exception // - only difference is that aObj is set to nil AFTER being destroyed procedure FreeAndNilSafe(var aObj); /// same as aInterface := nil but ignoring any exception procedure InterfaceNilSafe(var aInterface); /// same as aInterface := nil but ignoring any exception procedure InterfacesNilSafe(const aInterfaces: array of pointer); /// wrapper to add an item to a T*InterfaceArray dynamic array storage function InterfaceArrayAdd(var aInterfaceArray; const aItem: IUnknown): PtrInt; /// wrapper to add an item to a T*InterfaceArray dynamic array storage function InterfaceArrayAddCount(var aInterfaceArray; var aCount: integer; const aItem: IUnknown): PtrInt; /// wrapper to add once an item to a T*InterfaceArray dynamic array storage procedure InterfaceArrayAddOnce(var aInterfaceArray; const aItem: IUnknown); /// wrapper to search an item in a T*InterfaceArray dynamic array storage // - search is performed by address/reference, not by content // - return -1 if the item is not found in the dynamic array, or the index of // the matching entry otherwise function InterfaceArrayFind(const aInterfaceArray; const aItem: IUnknown): PtrInt; {$ifdef HASINLINE}inline;{$endif} /// wrapper to delete an item in a T*InterfaceArray dynamic array storage // - search is performed by address/reference, not by content // - do nothing if the item is not found in the dynamic array function InterfaceArrayDelete(var aInterfaceArray; const aItem: IUnknown): PtrInt; overload; /// wrapper to delete an item in a T*InterfaceArray dynamic array storage // - do nothing if the item is not found in the dynamic array procedure InterfaceArrayDelete(var aInterfaceArray; aItemIndex: PtrInt); overload; { ************ Low-level Types Mapping Binary Structures } type /// binary access to an unsigned 32-bit value (4 bytes in memory) TDWordRec = record case integer of 0: ( V: DWord); 1: ( L, H: word); 2: ( B: array[0..3] of byte); end; /// points to the binary of an unsigned 32-bit value PDWordRec = ^TDWordRec; /// binary access to an unsigned 64-bit value (8 bytes in memory) TQWordRec = record case integer of 0: ( V: Qword); 1: ( L, H: cardinal); 2: ( Li, Hi: integer); 3: ( W: array[0..3] of word); 4: ( B: array[0..7] of byte); end; /// points to the binary of an unsigned 64-bit value PQWordRec = ^TQWordRec; /// store a 128-bit hash value // - e.g. a MD5 digest, or array[0..3] of cardinal (TBlock128) // - consumes 16 bytes of memory THash128 = array[0..15] of byte; /// pointer to a 128-bit hash value PHash128 = ^THash128; /// store a 160-bit hash value // - e.g. a SHA-1 digest // - consumes 20 bytes of memory THash160 = array[0..19] of byte; /// pointer to a 160-bit hash value PHash160 = ^THash160; /// store a 192-bit hash value // - consumes 24 bytes of memory THash192 = array[0..23] of byte; /// pointer to a 192-bit hash value PHash192 = ^THash192; /// store a 224-bit hash value // - consumes 28 bytes of memory THash224 = array[0..27] of byte; /// pointer to a 224-bit hash value PHash224 = ^THash224; /// store a 256-bit hash value // - e.g. a SHA-256 digest, a TEccSignature result, or array[0..7] of cardinal // - consumes 32 bytes of memory THash256 = array[0..31] of byte; /// pointer to a 256-bit hash value PHash256 = ^THash256; /// store a 384-bit hash value // - e.g. a SHA-384 digest // - consumes 48 bytes of memory THash384 = array[0..47] of byte; /// pointer to a 384-bit hash value PHash384 = ^THash384; /// store a 512-bit hash value // - e.g. a SHA-512 digest, a TEccSignature result, or array[0..15] of cardinal // - consumes 64 bytes of memory THash512 = array[0..63] of byte; /// pointer to a 512-bit hash value PHash512 = ^THash512; /// store a 128-bit buffer // - e.g. an AES block // - consumes 16 bytes of memory TBlock128 = array[0..3] of cardinal; /// pointer to a 128-bit buffer PBlock128 = ^TBlock128; /// map an infinite array of 128-bit hash values // - each item consumes 16 bytes of memory THash128Array = array[ 0 .. MaxInt div SizeOf(THash128) - 1 ] of THash128; /// pointer to an infinite array of 128-bit hash values PHash128Array = ^THash128Array; /// store several 128-bit hash values // - e.g. MD5 digests // - consumes 16 bytes of memory per item THash128DynArray = array of THash128; /// map a 128-bit hash as an array of lower bit size values // - consumes 16 bytes of memory THash128Rec = packed record case integer of 0: ( Lo, Hi: Int64); 1: ( L, H: QWord); 2: ( i0, i1, i2, i3: integer); 3: ( c0, c1, c2 ,c3: cardinal); 4: ( c: TBlock128); 5: ( b: THash128); 6: ( w: array[0..7] of word); 7: ( l64, h64: Int64Rec); 8: ( guid: TGuid); end; /// pointer to 128-bit hash map variable record PHash128Rec = ^THash128Rec; /// map an infinite array of 256-bit hash values // - each item consumes 32 bytes of memory THash256Array = array[ 0 .. MaxInt div SizeOf(THash256) - 1 ] of THash256; /// pointer to an infinite array of 256-bit hash values PHash256Array = ^THash256Array; /// store several 256-bit hash values // - e.g. SHA-256 digests, TEccSignature results, or array[0..7] of cardinal // - consumes 32 bytes of memory per item THash256DynArray = array of THash256; /// map a 256-bit hash as an array of lower bit size values // - consumes 32 bytes of memory THash256Rec = packed record case integer of 0: ( Lo, Hi: THash128); 1: ( d0, d1, d2, d3: Int64); 2: ( i0, i1, i2, i3, i4, i5, i6, i7: integer); 3: ( c0, c1: TBlock128); 4: ( b: THash256); 5: ( q: array[0..3] of QWord); 6: ( c: array[0..7] of cardinal); 7: ( w: array[0..15] of word); 8: ( l, h: THash128Rec); 9: ( sha1: THash160); end; /// pointer to 256-bit hash map variable record PHash256Rec = ^THash256Rec; /// map an infinite array of 512-bit hash values // - each item consumes 64 bytes of memory THash512Array = array[ 0 .. MaxInt div SizeOf(THash512) - 1 ] of THash512; /// pointer to an infinite array of 512-bit hash values PHash512Array = ^THash512Array; /// store several 512-bit hash values // - e.g. SHA-512 digests, or array[0..15] of cardinal // - consumes 64 bytes of memory per item THash512DynArray = array of THash512; /// map a 512-bit hash as an array of lower bit size values // - consumes 64 bytes of memory THash512Rec = packed record case integer of 0: ( Lo, Hi: THash256); 1: ( h0, h1, h2, h3: THash128); 2: ( d0, d1, d2, d3, d4, d5, d6, d7: Int64); 3: ( i0, i1, i2, i3, i4, i5, i6, i7, i8, i9, i10, i11, i12, i13, i14, i15: integer); 4: ( c0, c1, c2, c3: TBlock128); 5: ( b: THash512); 6: ( b160: THash160); 7: ( b384: THash384); 8: ( w: array[0..31] of word); 9: ( c: array[0..15] of cardinal); 10: ( i: array[0..7] of Int64); 11: ( q: array[0..7] of QWord); 12: ( r: array[0..3] of THash128Rec); 13: ( l, h: THash256Rec); end; /// pointer to 512-bit hash map variable record PHash512Rec = ^THash512Rec; /// returns TRUE if all 16 bytes of this 128-bit buffer equal zero // - e.g. a MD5 digest, or an AES block function IsZero(const dig: THash128): boolean; overload; {$ifdef HASINLINE}inline;{$endif} /// returns TRUE if all 16 bytes of both 128-bit buffers do match // - e.g. a MD5 digest, or an AES block // - this function is not sensitive to any timing attack, so is designed // for cryptographic purpose - and it is also branchless therefore fast function IsEqual(const A, B: THash128): boolean; overload; {$ifdef HASINLINE}inline;{$endif} /// fill all 16 bytes of this 128-bit buffer with zero // - may be used to cleanup stack-allocated content // ! ... finally FillZero(digest); end; procedure FillZero(out dig: THash128); overload; /// fast O(n) search of a 128-bit item in an array of such values function Hash128Index(P: PHash128Rec; Count: integer; h: PHash128Rec): integer; /// add a 128-bit item in an array of such values function AddHash128(var Arr: THash128DynArray; const V: THash128; var Count: integer): PtrInt; /// returns TRUE if all 20 bytes of this 160-bit buffer equal zero // - e.g. a SHA-1 digest function IsZero(const dig: THash160): boolean; overload; {$ifdef HASINLINE}inline;{$endif} /// returns TRUE if all 20 bytes of both 160-bit buffers do match // - e.g. a SHA-1 digest // - this function is not sensitive to any timing attack, so is designed // for cryptographic purpose function IsEqual(const A, B: THash160): boolean; overload; {$ifdef HASINLINE}inline;{$endif} /// fill all 20 bytes of this 160-bit buffer with zero // - may be used to cleanup stack-allocated content // ! ... finally FillZero(digest); end; procedure FillZero(out dig: THash160); overload; /// returns TRUE if all 32 bytes of this 256-bit buffer equal zero // - e.g. a SHA-256 digest, or a TEccSignature result function IsZero(const dig: THash256): boolean; overload; {$ifdef HASINLINE}inline;{$endif} /// returns TRUE if all 32 bytes of both 256-bit buffers do match // - e.g. a SHA-256 digest, or a TEccSignature result // - this function is not sensitive to any timing attack, so is designed // for cryptographic purpose function IsEqual(const A, B: THash256): boolean; overload; {$ifdef HASINLINE}inline;{$endif} /// fast O(n) search of a 256-bit item in an array of such values function Hash256Index(P: PHash256Rec; Count: integer; h: PHash256Rec): integer; /// fill all 32 bytes of this 256-bit buffer with zero // - may be used to cleanup stack-allocated content // ! ... finally FillZero(digest); end; procedure FillZero(out dig: THash256); overload; /// returns TRUE if all 48 bytes of this 384-bit buffer equal zero // - e.g. a SHA-384 digest function IsZero(const dig: THash384): boolean; overload; {$ifdef HASINLINE}inline;{$endif} /// returns TRUE if all 48 bytes of both 384-bit buffers do match // - e.g. a SHA-384 digest // - this function is not sensitive to any timing attack, so is designed // for cryptographic purpose function IsEqual(const A, B: THash384): boolean; overload; {$ifdef HASINLINE}inline;{$endif} /// fill all 32 bytes of this 384-bit buffer with zero // - may be used to cleanup stack-allocated content // ! ... finally FillZero(digest); end; procedure FillZero(out dig: THash384); overload; /// returns TRUE if all 64 bytes of this 512-bit buffer equal zero // - e.g. a SHA-512 digest function IsZero(const dig: THash512): boolean; overload; {$ifdef HASINLINE}inline;{$endif} /// returns TRUE if all 64 bytes of both 512-bit buffers do match // - e.g. two SHA-512 digests // - this function is not sensitive to any timing attack, so is designed // for cryptographic purpose function IsEqual(const A, B: THash512): boolean; overload; {$ifdef HASINLINE}inline;{$endif} /// fill all 64 bytes of this 512-bit buffer with zero // - may be used to cleanup stack-allocated content // ! ... finally FillZero(digest); end; procedure FillZero(out dig: THash512); overload; /// returns TRUE if all bytes of both buffers do match // - this function is not sensitive to any timing attack, so is designed // for cryptographic purposes - use CompareMem/CompareMemSmall/CompareMemFixed // as faster alternatives for general-purpose code function IsEqual(const A, B; count: PtrInt): boolean; overload; /// thread-safe move of a 32-bit value using a simple Read-Copy-Update pattern procedure Rcu32(var src, dst); /// thread-safe move of a 64-bit value using a simple Read-Copy-Update pattern procedure Rcu64(var src, dst); /// thread-safe move of a 128-bit value using a simple Read-Copy-Update pattern procedure Rcu128(var src, dst); /// thread-safe move of a pointer value using a simple Read-Copy-Update pattern procedure RcuPtr(var src, dst); /// thread-safe move of a memory buffer using a simple Read-Copy-Update pattern procedure Rcu(var src, dst; len: integer); {$ifdef ISDELPHI} /// this function is an intrinsic in FPC procedure ReadBarrier; {$ifndef CPUINTEL} inline; {$endif} {$endif ISDELPHI} /// fast computation of two 64-bit unsigned integers into a 128-bit value {$ifdef CPUINTEL} procedure mul64x64(const left, right: QWord; out product: THash128Rec); {$else} procedure mul64x64(constref left, right: QWord; out product: THash128Rec); inline; {$endif CPUINTEL} { ************ Low-level Functions Manipulating Bits } /// retrieve a particular bit status from a bit array // - this function can't be inlined, whereas GetBitPtr() function can function GetBit(const Bits; aIndex: PtrInt): boolean; /// set a particular bit into a bit array // - this function can't be inlined, whereas SetBitPtr() function can procedure SetBit(var Bits; aIndex: PtrInt); /// unset/clear a particular bit into a bit array // - this function can't be inlined, whereas UnSetBitPtr() function can procedure UnSetBit(var Bits; aIndex: PtrInt); /// retrieve a particular bit status from a bit array // - GetBit() can't be inlined, whereas this pointer-oriented function can function GetBitPtr(Bits: pointer; aIndex: PtrInt): boolean; {$ifdef HASINLINE}inline;{$endif} /// set a particular bit into a bit array // - SetBit() can't be inlined, whereas this pointer-oriented function can procedure SetBitPtr(Bits: pointer; aIndex: PtrInt); {$ifdef HASINLINE}inline;{$endif} /// unset/clear a particular bit into a bit array // - UnSetBit() can't be inlined, whereas this pointer-oriented function can procedure UnSetBitPtr(Bits: pointer; aIndex: PtrInt); {$ifdef HASINLINE}inline;{$endif} /// compute the number of bits set in a bit array // - Count is the number of BITS to check, not the byte size // - will use fast SSE4.2 popcnt instruction if available on the CPU function GetBitsCount(const Bits; Count: PtrInt): PtrInt; /// pure pascal version of GetBitsCountPtrInt() // - defined just for regression tests - call GetBitsCountPtrInt() instead // - has optimized asm on x86_64 and i386 function GetBitsCountPas(value: PtrInt): PtrInt; /// compute how many bits are set in a given pointer-sized integer // - the PopCnt() intrinsic under FPC doesn't have any fallback on older CPUs, // and default implementation is 5 times slower than our GetBitsCountPas() on x64 // - this redirected function will use fast SSE4.2 "popcnt" opcode, if available var GetBitsCountPtrInt: function(value: PtrInt): PtrInt = GetBitsCountPas; /// compute how many bytes are needed to store a given number of bits // - e.g. 0 returns 0, 1..8 returns 1, 9..16 returns 2, and so on function BitsToBytes(bits: byte): byte; {$ifdef HASINLINE}inline;{$endif} const /// could be used to compute the index in a pointer list from its byte position POINTERSHR = {$ifdef CPU64} 3 {$else} 2 {$endif}; /// could be used to compute the bitmask of a pointer integer POINTERAND = {$ifdef CPU64} 7 {$else} 3 {$endif}; /// could be used to check all bits on a pointer POINTERBITS = {$ifdef CPU64} 64 {$else} 32 {$endif}; /// could be used to check all bytes on a pointer POINTERBYTES = {$ifdef CPU64} 8 {$else} 4 {$endif}; /// could be used to compute the index in a pointer list from its bits position POINTERSHRBITS = {$ifdef CPU64} 6 {$else} 5 {$endif}; /// constant array used by GetAllBits() function (when inlined) ALLBITS_CARDINAL: array[1..32] of cardinal = ( 1 shl 1 - 1, 1 shl 2 - 1, 1 shl 3 - 1, 1 shl 4 - 1, 1 shl 5 - 1, 1 shl 6 - 1, 1 shl 7 - 1, 1 shl 8 - 1, 1 shl 9 - 1, 1 shl 10 - 1, 1 shl 11 - 1, 1 shl 12 - 1, 1 shl 13 - 1, 1 shl 14 - 1, 1 shl 15 - 1, 1 shl 16 - 1, 1 shl 17 - 1, 1 shl 18 - 1, 1 shl 19 - 1, 1 shl 20 - 1, 1 shl 21 - 1, 1 shl 22 - 1, 1 shl 23 - 1, 1 shl 24 - 1, 1 shl 25 - 1, 1 shl 26 - 1, 1 shl 27 - 1, 1 shl 28 - 1, 1 shl 29 - 1, 1 shl 30 - 1, $7fffffff, $ffffffff); /// returns TRUE if all BitCount bits are set in the input 32-bit cardinal function GetAllBits(Bits, BitCount: cardinal): boolean; {$ifdef HASINLINE}inline;{$endif} type /// fast access to 8-bit integer bits // - compiler will generate bt/btr/bts opcodes - note: they may be slow when // applied on a memory location, but not on a byte value (register) TBits8 = set of 0..7; PBits8 = ^TBits8; TBits8Array = array[ 0 .. MaxInt - 1 ] of TBits8; /// fast access to 32-bit integer bits // - compiler will generate bt/btr/bts opcodes - note: they may be slow when // applied on a memory location, but not on an integer value (register) TBits32 = set of 0..31; PBits32 = ^TBits32; /// fast access to 64-bit integer bits // - compiler will generate bt/btr/bts opcodes - note: they may be slow when // applied on a memory location, but not on a Int64 value (register) // - as used by GetBit64/SetBit64/UnSetBit64 TBits64 = set of 0..63; PBits64 = ^TBits64; /// retrieve a particular bit status from a 64-bit integer bits (max aIndex is 63) function GetBit64(const Bits: Int64; aIndex: PtrInt): boolean; {$ifdef HASINLINE}inline;{$endif} /// set a particular bit into a 64-bit integer bits (max aIndex is 63) procedure SetBit64(var Bits: Int64; aIndex: PtrInt); {$ifdef HASINLINE}inline;{$endif} /// unset/clear a particular bit into a 64-bit integer bits (max aIndex is 63) procedure UnSetBit64(var Bits: Int64; aIndex: PtrInt); {$ifdef HASINLINE}inline;{$endif} { ************ Faster Alternative to RTL Standard Functions } type /// the potential features, retrieved from an Intel/AMD CPU // - cf https://en.wikipedia.org/wiki/CPUID#EAX.3D1:_Processor_Info_and_Feature_Bits // - is defined on all platforms, so that e.g. an ARM desktop may browse // Intel-generated logs using TSynLogFile from mormot.core.log.pas TIntelCpuFeature = ( { CPUID EAX=1 into EDX, ECX } cfFPU, cfVME, cfDE, cfPSE, cfTSC, cfMSR, cfPAE, cfMCE, cfCX8, cfAPIC, cf_d10, cfSEP, cfMTRR, cfPGE, cfMCA, cfCMOV, cfPAT, cfPSE36, cfPSN, cfCLFSH, cf_d20, cfDS, cfACPI, cfMMX, cfFXSR, cfSSE, cfSSE2, cfSS, cfHTT, cfTM, cfIA64, cfPBE, cfSSE3, cfCLMUL, cfDS64, cfMON, cfDSCPL, cfVMX, cfSMX, cfEST, cfTM2, cfSSSE3, cfCID, cfSDBG, cfFMA, cfCX16, cfXTPR, cfPDCM, cf_c16, cfPCID, cfDCA, cfSSE41, cfSSE42, cfX2A, cfMOVBE, cfPOPCNT, cfTSC2, cfAESNI, cfXS, cfOSXS, cfAVX, cfF16C, cfRAND, cfHYP, { extended features CPUID EAX=7,ECX=0 into EBX, ECX, EDX } cfFSGS, cfTSCADJ, cfSGX, cfBMI1, cfHLE, cfAVX2, cfFDPEO, cfSMEP, cfBMI2, cfERMS, cfINVPCID, cfRTM, cfPQM, cfFPUSEG, cfMPX, cfPQE, cfAVX512F, cfAVX512DQ, cfRDSEED, cfADX, cfSMAP, cfAVX512IFMA, cfPCOMMIT, cfCLFLUSH, cfCLWB, cfIPT, cfAVX512PF, cfAVX512ER, cfAVX512CD, cfSHA, cfAVX512BW, cfAVX512VL, cfPREFW1, cfAVX512VBMI, cfUMIP, cfPKU, cfOSPKE, cfWAITPKG, cfAVX512VBMI2, cfCETSS, cfGFNI, cfVAES, cfVCLMUL, cfAVX512NNI, cfAVX512BITALG, cfTMEEN, cfAVX512VPC, cf_c15, cfFLP, cfMPX0, cfMPX1, cfMPX2, cfMPX3, cfMPX4, cfRDPID, cfKL, cfBUSLOCK, cfCLDEMOTE, cf_c26, cfMOVDIRI, cfMOVDIR64B, cfENQCMD, cfSGXLC, cfPKS, cf_d0, cfSGXKEYS, cfAVX512NNIW, cfAVX512MAPS, cfFSRM, cfUINTR, cf_d6, cf_d7, cfAVX512VP2I, cfSRBDS, cfMDCLR, cfTSXABRT, cf_d12, cfTSXFA, cfSER, cfHYBRID, cfTSXLDTRK, cf_d17, cfPCFG, cfLBR, cfIBT, cf_d21, cfAMXBF16, cfAVX512FP16, cfAMXTILE, cfAMXINT8, cfIBRSPB, cfSTIBP, cfL1DFL, cfARCAB, cfCORCAB, cfSSBD, { extended features CPUID EAX=7,ECX=1 into EAX, EDX } cfSHA512, cfSM3, cfSM4, cfRAOINT, cfAVXVNNI, cfAVX512BF16, cfLASS, cfCMPCCXADD, cfAPMEL, cf_a9, cfFZLREPM, cfFSREPS, cfFSREPC, cf_a13, cf_a14, cf_a15, cf_a16, cfFRED, cfLKGS, cfWRMSRNS, cf_a20, cfAMXFP16, cfHRESET, cfAVXIFMA, cf_a24, cf_a25, cfLAM, cfMSRLIST, cf_a28, cf_a29, cf_a30, cf_a31, cf__d0, cf_d1, cf_d2, cf_d3, cfAVXVNN8, cfAVXNECVT, cf__d6, cf__d7, cfAMXCPLX, cf_d9, cfAVXVNNI16, cf_d11, cf__d12, cf_d13, cfPREFETCHI, cf_d15, cf_d16, cfUIRETUIF, cfCETSSS, cfAVX10, cf__d20, cf_APXF, cf_d22, cf_d23, cf_d24, cf_d25, cf_d26, cf_d27, cf_d28, cf_d29, cf_d30, cf_d31); /// all CPU features flags, as retrieved from an Intel/AMD CPU TIntelCpuFeatures = set of TIntelCpuFeature; /// the supported AVX10 Converged Vector ISA bit sizes TIntelAvx10Vector = set of ( av128, av256, av512); /// the AVX10 Converged Vector ISA features TIntelAvx10Features = record /// maximum supported sub-leaf MaxSubLeaf: cardinal; /// the ISA version (>= 1) Version: byte; /// bit vector size support Vector: TIntelAvx10Vector; end; /// 32-bit ARM Hardware capabilities // - merging AT_HWCAP and AT_HWCAP2 flags as reported by // github.com/torvalds/linux/blob/master/arch/arm/include/uapi/asm/hwcap.h // - is defined on all platforms for cross-system use TArm32HwCap = ( // HWCAP_* constants arm32SWP, arm32HALF, arm32THUMB, arm3226BIT, arm32FAST_MULT, arm32FPA, arm32VFP, arm32EDSP, arm32JAVA, arm32IWMMXT, arm32CRUNCH, arm32THUMBEE, arm32NEON, arm32VFPv3, arm32VFPv3D16, arm32TLS, arm32VFPv4, arm32IDIVA, arm32IDIVT, arm32VFPD32, arm32LPAE, arm32EVTSTRM, arm32_22, arm32_23, arm32_24, arm32_25, arm32_26, arm32_27, arm32_28, arm32_29, arm32_30, arm32_31, // HWCAP2_* constants arm32AES, arm32PMULL, arm32SHA1, arm32SHA2, arm32CRC32); TArm32HwCaps = set of TArm32HwCap; /// 64-bit AARCH64 Hardware capabilities // - merging AT_HWCAP and AT_HWCAP2 flags as reported by // github.com/torvalds/linux/blob/master/arch/arm64/include/uapi/asm/ahccap.h // - is defined on all platforms for cross-system use TArm64HwCap = ( // HWCAP_* constants arm64FP, arm64ASIMD, arm64EVTSTRM, arm64AES, arm64PMULL, arm64SHA1, arm64SHA2, arm64CRC32, arm64ATOMICS, arm64FPHP, arm64ASIMDHP, arm64CPUID, arm64ASIMDRDM, arm64JSCVT, arm64FCMA, arm64LRCPC, arm64DCPOP, arm64SHA3, arm64SM3, arm64SM4, arm64ASIMDDP, arm64SHA512, arm64SVE, arm64ASIMDFHM, arm64DIT, arm64USCAT, arm64ILRCPC, arm64FLAGM, arm64SSBS, arm64SB, arm64PACA, arm64PACG, // HWCAP2_* constants arm64DCPODP, arm64SVE2, arm64SVEAES, arm64SVEPMULL, arm64SVEBITPERM, arm64SVESHA3, arm64SVESM4, arm64FLAGM2, arm64FRINT, arm64SVEI8MM, arm64SVEF32MM, arm64SVEF64MM, arm64SVEBF16, arm64I8MM, arm64BF16, arm64DGH, arm64RNG, arm64BTI, arm64MTE); TArm64HwCaps = set of TArm64HwCap; {$ifdef CPUARM} TArmHwCap = TArm32HwCap; TArmHwCaps = TArm32HwCaps; const ahcAES = arm32AES; ahcPMULL = arm32PMULL; ahcSHA1 = arm32SHA1; ahcSHA2 = arm32SHA2; ahcCRC32 = arm32CRC32; {$endif CPUARM} {$ifdef CPUAARCH64} TArmHwCap = TArm64HwCap; TArmHwCaps = TArm64HwCaps; const ahcAES = arm64AES; ahcPMULL = arm64PMULL; ahcSHA1 = arm64SHA1; ahcSHA2 = arm64SHA2; ahcCRC32 = arm64CRC32; {$endif CPUAARCH64} {$ifdef CPUARM3264} var /// the low-level ARM/AARCH64 CPU features retrieved from system.envp // - text from CpuInfoFeatures may not be accurate on oldest kernels CpuFeatures: TArmHwCaps; {$endif CPUARM3264} /// cross-platform wrapper function to check AES HW support on Intel or ARM function HasHWAes: boolean; {$ifdef CPUINTEL} var /// the available Intel/AMD CPU features, as recognized at program startup // - on LINUX, consider CpuInfoArm or the textual CpuInfoFeatures from // mormot.core.os.pas CpuFeatures: TIntelCpuFeatures; /// the detected AVX10 Converged Vector ISA features // - only set if cfAVX10 is part of CpuFeatures CpuAvx10: TIntelAvx10Features; /// compute 32-bit random number generated by modern Intel CPU hardware // - using NIST SP 800-90A and FIPS 140-2 compliant RDRAND Intel x86/x64 opcode // - caller should ensure that cfSSE42 is included in CpuFeatures flags // - you should rather call XorEntropy() which offers additional sources function RdRand32: cardinal; overload; /// XOR a memory buffer with some random generated by modern Intel CPU // - n is the number of 32-bit slots in the supplied buffer to fill // - will do nothing if cfSSE42 is not available on this CPU procedure RdRand32(buffer: PCardinal; n: integer); overload; /// returns the 64-bit Intel Time Stamp Counter (TSC) // - could be used as entropy source for randomness - use TPrecisionTimer if // you expect a cross-platform and cross-CPU high resolution performance counter function Rdtsc: Int64; /// compatibility function, to be implemented according to the running CPU // - expect the same result as the homonymous Win32 API function, i.e. // returns I + 1, and store I + 1 within I in an atomic/tread-safe way // - FPC will define this function as intrinsic for non-Intel CPUs function InterlockedIncrement(var I: integer): integer; /// compatibility function, to be implemented according to the running CPU // - expect the same result as the homonymous Win32 API function, i.e. // returns I - 1, and store I - 1 within I in an atomic/tread-safe way // - FPC will define this function as intrinsic for non-Intel CPUs function InterlockedDecrement(var I: integer): integer; /// slightly faster than InterlockedIncrement() when you don't need the result procedure LockedInc32(int32: PInteger); /// slightly faster than InterlockedDecrement() when you don't need the result procedure LockedDec32(int32: PInteger); /// slightly faster than InterlockedIncrement64() procedure LockedInc64(int64: PInt64); // defined here for mormot.test.base only function GetBitsCountSSE42(value: PtrInt): PtrInt; // defined here for mormot.test.base only // - use instead global crc32c() variable function crc32csse42(crc: cardinal; buf: PAnsiChar; len: cardinal): cardinal; {$else} /// redirect to FPC InterlockedIncrement() on non Intel CPU procedure LockedInc32(int32: PInteger); inline; /// redirect to FPC InterlockedDecrement() on non Intel CPU procedure LockedDec32(int32: PInteger); inline; /// redirect to FPC InterlockedIncrement64() on non Intel CPU procedure LockedInc64(int64: PInt64); inline; {$endif CPUINTEL} /// low-level string reference counter unprocess // - caller should have tested that refcnt>=0 // - returns true if the managed variable should be released (i.e. refcnt was 1) function StrCntDecFree(var refcnt: TStrCnt): boolean; {$ifndef CPUINTEL} inline; {$endif} /// low-level dynarray reference counter unprocess // - caller should have tested that refcnt>=0 function DACntDecFree(var refcnt: TDACnt): boolean; {$ifndef CPUINTEL} inline; {$endif} /// low-level string reference counter process procedure StrCntAdd(var refcnt: TStrCnt; increment: TStrCnt); {$ifdef HASINLINE} inline; {$endif} /// low-level dynarray reference counter process procedure DACntAdd(var refcnt: TDACnt; increment: TDACnt); {$ifdef HASINLINE} inline; {$endif} /// fast atomic compare-and-swap operation on a pointer-sized integer value // - via Intel/AMD custom asm or FPC RTL InterlockedCompareExchange(pointer) // - true if Target was equal to Comparand, and Target set to NewValue // - used e.g. as thread-safe atomic operation for TLightLock/TRWLock // - Target should be aligned, which is the case when defined as a class field function LockedExc(var Target: PtrUInt; NewValue, Comperand: PtrUInt): boolean; {$ifndef CPUINTEL} inline; {$endif} /// fast atomic addition operation on a pointer-sized integer value // - via Intel/AMD custom asm or FPC RTL InterlockedExchangeAdd(pointer) // - Target should be aligned, which is the case when defined as a class field procedure LockedAdd(var Target: PtrUInt; Increment: PtrUInt); {$ifndef CPUINTEL} inline; {$endif} /// fast atomic substraction operation on a pointer-sized integer value // - via Intel/AMD custom asm or FPC RTL InterlockedExchangeAdd(-pointer) // - Target should be aligned, which is the case when defined as a class field procedure LockedDec(var Target: PtrUInt; Decrement: PtrUInt); {$ifndef CPUINTEL} inline; {$endif} /// fast atomic addition operation on a 32-bit integer value // - via Intel/AMD custom asm or FPC RTL InterlockedExchangeAdd(pointer) // - Target should be aligned, which is the case when defined as a class field procedure LockedAdd32(var Target: cardinal; Increment: cardinal); {$ifndef CPUINTEL} inline; {$endif} {$ifdef ISDELPHI} /// return the position of the leftmost set bit in a 32-bit value // - returns 255 if c equals 0 // - this function is an intrinsic on FPC function BSRdword(c: cardinal): cardinal; /// return the position of the leftmost set bit in a 64-bit value // - returns 255 if q equals 0 // - this function is an intrinsic on FPC function BSRqword(const q: Qword): cardinal; {$endif ISDELPHI} {$ifdef ASMINTEL} {$ifdef ASMX64} // will define its own self-dispatched SSE2/AVX functions type /// most common x86_64 CPU abilities, used e.g. by FillCharFast/MoveFast // - cpuHaswell identifies Intel/AMD AVX2+BMI support at Haswell level // as expected e.g. by IsValidUtf8Avx2/Base64EncodeAvx2 dedicated asm // - won't include ERMSB flag because it is not propagated within some VMs TX64CpuFeatures = set of ( cpuAVX, cpuAVX2, cpuHaswell); var /// internal flags used by FillCharFast - easier from asm that CpuFeatures X64CpuFeatures: TX64CpuFeatures; {$ifdef ASMX64AVXNOCONST} /// simdjson asm as used by mormot.core.unicode on Haswell for FPC IsValidUtf8() function IsValidUtf8Avx2(source: PUtf8Char; sourcelen: PtrInt): boolean; // avx2 asm as used by mormot.core.buffers for Base64EncodeMain/Base64DecodeMain procedure Base64EncodeAvx2(var b: PAnsiChar; var blen: PtrUInt; var b64: PAnsiChar); procedure Base64DecodeAvx2(var b64: PAnsiChar; var b64len: PtrInt; var b: PAnsiChar); {$endif ASMX64AVXNOCONST} {$endif ASMX64} /// our fast version of FillChar() on Intel/AMD // - on Intel i386/x86_64, will use fast SSE2/AVX instructions (if available) // - on non-Intel CPUs, it will fallback to the default RTL FillChar() // - note: Delphi RTL is far from efficient: on i386 the FPU is slower/unsafe, // and on x86_64, ERMS is wrongly used even for small blocks // - on ARM/AARCH64 POSIX, mormot.core.os would redirect to optimized libc procedure FillcharFast(var dst; cnt: PtrInt; value: byte); /// our fast version of move() on Intel/AMD // - on Delphi Intel i386/x86_64, will use fast SSE2 instructions (if available) // - FPC i386 has fastmove.inc which is faster than our SSE2/ERMS version // - FPC x86_64 RTL is slower than our SSE2/AVX asm // - on non-Intel CPUs, it will fallback to the default RTL Move() // - on ARM/AARCH64 POSIX, mormot.core.os would redirect to optimized libc {$ifdef FPC_X86} var MoveFast: procedure(const Source; var Dest; Count: PtrInt) = Move; {$else} procedure MoveFast(const src; var dst; cnt: PtrInt); {$endif FPC_X86} {$else} // fallback to RTL versions on non-INTEL or PIC platforms by default // and mormot.core.os.posix.inc redirects them to libc memset/memmove var FillcharFast: procedure(var Dest; count: PtrInt; Value: byte) = FillChar; var MoveFast: procedure(const Source; var Dest; Count: PtrInt) = Move; {$endif ASMINTEL} /// Move() with one-by-one byte copy // - never redirect to MoveFast() so could be used when data overlaps procedure MoveByOne(Source, Dest: Pointer; Count: PtrUInt); {$ifdef HASINLINE} inline; {$endif} /// perform a MoveFast then fill the Source buffer with zeros // - could be used e.g. to quickly move a managed record content into a newly // allocated stack variable with no reference counting procedure MoveAndZero(Source, Dest: Pointer; Count: PtrUInt); /// fill all bytes of a memory buffer with zero // - just redirect to FillCharFast(..,...,0) procedure FillZero(var dest; count: PtrInt); overload; {$ifdef HASINLINE}inline;{$endif} /// fill first bytes of a memory buffer with zero // - Length is expected to be not 0, typically in 1..8 range // - when inlined, is slightly more efficient than regular FillZero/FillCharFast procedure FillZeroSmall(P: pointer; Length: PtrInt); {$ifdef HASINLINE}inline;{$endif} /// binary comparison of buffers, returning <0, 0 or >0 results // - caller should ensure that P1<>nil, P2<>nil and L>0 // - on x86_64, will use a fast SSE2 asm version of the C function memcmp() // (which is also used by CompareMem and CompareBuf) // - on other platforms, run a simple but efficient per-byte comparison function MemCmp(P1, P2: PByteArray; L: PtrInt): integer; {$ifndef CPUX64} {$ifdef HASINLINE} inline; {$endif} {$endif} /// our fast version of CompareMem() // - tuned asm for x86, call MemCmpSse2 for x64, or fallback to tuned pascal function CompareMem(P1, P2: Pointer; Length: PtrInt): boolean; {$ifdef CPUX64}inline;{$endif} /// overload wrapper of MemCmp() to compare a RawByteString vs a memory buffer // - will first check length(P1)=P2Len then call MemCmp() function CompareBuf(const P1: RawByteString; P2: Pointer; P2Len: PtrInt): integer; overload; {$ifdef HASINLINE}inline;{$endif} /// overload wrapper to SortDynArrayRawByteString(P1, P2) // - won't involve any code page - so may be safer e.g. on FPC function CompareBuf(const P1, P2: RawByteString): integer; overload; {$ifdef HASINLINE}inline;{$endif} /// overload wrapper to SortDynArrayRawByteString(P1, P2) = 0 // - won't involve any code page - so may be safer e.g. on FPC function EqualBuf(const P1, P2: RawByteString): boolean; overload; {$ifdef HASINLINE}inline;{$endif} {$ifdef HASINLINE} function CompareMemFixed(P1, P2: Pointer; Length: PtrInt): boolean; inline; {$else} /// a CompareMem()-like function designed for small and fixed-sized content // - here, Length is expected to be a constant value - typically from SizeOf() - // so that inlining has better performance than calling the CompareMem() function var CompareMemFixed: function(P1, P2: Pointer; Length: PtrInt): boolean = CompareMem; {$endif HASINLINE} /// a CompareMem()-like function designed for small (a few bytes) content // - to be efficiently inlined in processing code function CompareMemSmall(P1, P2: Pointer; Length: PtrInt): boolean; {$ifdef HASINLINE}inline;{$endif} {$ifndef CPUX86} /// low-level efficient pure pascal function used when inlining PosEx() // - not to be called directly function PosExPas(pSub, p: PUtf8Char; Offset: PtrUInt): PtrInt; {$endif CPUX86} {$ifdef UNICODE} /// low-level efficient pure pascal function used when inlining PosExString() // - not to be called directly function PosExStringPas(pSub, p: PChar; Offset: PtrUInt): PtrInt; {$endif UNICODE} /// faster RawUtf8 Equivalent of standard StrUtils.PosEx function PosEx(const SubStr, S: RawUtf8; Offset: PtrUInt = 1): PtrInt; {$ifndef CPUX86}{$ifdef HASINLINE}inline;{$endif}{$endif} /// our own PosEx() function dedicated to RTL string process // - Delphi XE or older don't support Pos() with an Offset function PosExString(const SubStr, S: string; Offset: PtrUInt = 1): PtrInt; {$ifdef HASINLINE}inline;{$endif} /// optimized version of PosEx() with search text as one AnsiChar // - will use fast SSE2 asm on i386 and x86_64 function PosExChar(Chr: AnsiChar; const Str: RawUtf8): PtrInt; {$ifdef HASINLINE}inline;{$endif} /// fast retrieve the position of a given character in a #0 ended buffer // - will use fast SSE2 asm on i386 and x86_64 function PosChar(Str: PUtf8Char; Chr: AnsiChar): PUtf8Char; overload; {$ifndef CPUX64}{$ifdef FPC}inline;{$endif}{$endif} /// fast retrieve the position of a given character in a #0 ended buffer // - will use fast SSE2 asm on i386 and x86_64 function PosChar(Str: PUtf8Char; StrLen: PtrInt; Chr: AnsiChar): PUtf8Char; overload; {$ifdef HASINLINE}inline;{$endif} {$ifndef PUREMORMOT2} /// fast dedicated RawUtf8 version of Trim() // - in the middle of UI code, consider using TrimU() which won't have name // collision ambiguity as with SysUtils' homonymous function function Trim(const S: RawUtf8): RawUtf8; {$ifdef HASINLINE}inline;{$endif} {$endif PUREMORMOT2} /// fast dedicated RawUtf8 version of Trim() // - should be used for RawUtf8 instead of SysUtils' Trim() which is ambiguous // with the main String/UnicodeString type of Delphi 2009+ // - in mORMot 1.18, there was a Trim() function but it was confusing function TrimU(const S: RawUtf8): RawUtf8; /// fast dedicated RawUtf8 version of s := Trim(s) procedure TrimSelf(var S: RawUtf8); /// single-allocation (therefore faster) alternative to Trim(copy()) procedure TrimCopy(const S: RawUtf8; start, count: PtrInt; var result: RawUtf8); /// returns the left part of a RawUtf8 string, according to SepStr separator // - if SepStr is found, returns Str first chars until (and excluding) SepStr // - if SepStr is not found, returns Str function Split(const Str, SepStr: RawUtf8; StartPos: PtrInt = 1): RawUtf8; overload; /// buffer-overflow safe version of StrComp(), to be used with PUtf8Char/PAnsiChar function StrComp(Str1, Str2: pointer): PtrInt; {$ifndef CPUX86}{$ifdef HASINLINE}inline;{$endif}{$endif} /// our fast version of StrComp(), to be used with PWideChar function StrCompW(Str1, Str2: PWideChar): PtrInt; {$ifdef HASINLINE}inline;{$endif} /// simple version of StrLen(), but which will never read beyond the string // - this version won't access the memory beyond the string, so may be // preferred e.g. with valgrid // - SSE2 StrLen() versions would never read outside a memory page boundary, // so are safe to use in practice, but may read outside the string buffer // itself, so may not please paranoid tools like valgrid function StrLenSafe(S: pointer): PtrInt; {$ifdef CPU64}inline;{$endif} /// our fast version of StrLen(), to be used with PUtf8Char/PAnsiChar // - under x86, will detect SSE2 and use it if available, reaching e.g. // 37.5 GB/s on a Core i5-13500 under Linux x86_64 // - on ARM/AARCH64 POSIX, mormot.core.os would redirect to optimized libc {$ifdef CPUX64} function StrLen(S: pointer): PtrInt; {$else} var StrLen: function(S: pointer): PtrInt = StrLenSafe; {$endif CPUX64} /// our fast version of StrLen(), to be used with PWideChar function StrLenW(S: PWideChar): PtrInt; /// fast go to next text line, ended by #13 or #13#10 // - source is expected to be not nil // - returns the beginning of next line, or nil if source^=#0 was reached function GotoNextLine(source: PUtf8Char): PUtf8Char; {$ifdef HASINLINE}inline;{$endif} /// fast go to the first char <= #13 // - source is expected to be not nil function GotoNextControlChar(source: PUtf8Char): PUtf8Char; {$ifdef HASINLINE}inline;{$endif} /// return TRUE if the supplied buffer only contains 7-bits Ansi characters function IsAnsiCompatible(PC: PAnsiChar): boolean; overload; /// return TRUE if the supplied buffer only contains 7-bits Ansi characters function IsAnsiCompatible(PC: PAnsiChar; Len: PtrUInt): boolean; overload; /// return TRUE if the supplied UTF-16 buffer only contains 7-bits Ansi characters function IsAnsiCompatibleW(PW: PWideChar): boolean; overload; /// return TRUE if the supplied text only contains 7-bits Ansi characters function IsAnsiCompatible(const Text: RawByteString): boolean; overload; {$ifdef HASINLINE}inline;{$endif} /// return TRUE if the supplied UTF-16 buffer only contains 7-bits Ansi characters function IsAnsiCompatibleW(PW: PWideChar; Len: PtrInt): boolean; overload; type /// 32-bit Pierre L'Ecuyer software (random) generator // - cross-compiler and cross-platform efficient randomness generator, very // fast with a much better distribution than Delphi system's Random() function // see https://www.gnu.org/software/gsl/doc/html/rng.html#c.gsl_rng_taus2 // - used by thread-safe Random32/RandomBytes, storing 16 bytes per thread - a // stronger algorithm like Mersenne Twister (as used by FPC RTL) requires 5KB // - SeedGenerator() makes it a sequence generator - or encryptor via Fill() // - when used as random generator (default when initialized with 0), Seed() // will gather and hash some system entropy to initialize the internal state {$ifdef USERECORDWITHMETHODS} TLecuyer = record {$else} TLecuyer = object {$endif USERECORDWITHMETHODS} public rs1, rs2, rs3, seedcount: cardinal; /// force a random seed of the generator from current system state // - as executed by the Next method at thread startup, and after 2^32 values // - calls XorEntropy(), so RdRand32/Rdtsc opcodes on Intel/AMD CPUs procedure Seed(entropy: PByteArray = nil; entropylen: PtrInt = 0); /// force a well-defined seed of the generator from a fixed initial point // - to be called before Next/Fill to generate the very same output // - will generate up to 16GB of predictable output, then switch to random procedure SeedGenerator(fixedseed: QWord); overload; /// force a well-defined seed of the generator from a buffer initial point // - apply crc32c() over the fixedseed buffer to initialize the generator procedure SeedGenerator(fixedseed: pointer; fixedseedbytes: integer); overload; /// compute the next 32-bit generated value with no Seed - internal call function RawNext: cardinal; /// compute the next 32-bit generated value // - will automatically reseed after around 2^32 generated values, which is // huge but very conservative since this generator has a period of 2^88 function Next: cardinal; overload; {$ifdef HASSAFEINLINE}inline;{$endif} /// compute the next 32-bit generated value, in range [0..max-1] function Next(max: cardinal): cardinal; overload; {$ifdef HASSAFEINLINE}inline;{$endif} /// compute a 64-bit integer value function NextQWord: QWord; /// compute a 64-bit floating point value function NextDouble: double; /// XOR some memory buffer with random bytes // - when used as sequence generator after SeedGenerator(), dest buffer // should be filled with zeros before the call if you want to use it as // generator, but could be applied on any memory buffer for encryption procedure Fill(dest: pointer; bytes: integer); /// fill some string[0..size] with 7-bit ASCII random text procedure FillShort(var dest: ShortString; size: PtrUInt = 255); /// fill some string[0..31] with 7-bit ASCII random text procedure FillShort31(var dest: TShort31); end; PLecuyer = ^TLecuyer; /// return the 32-bit Pierre L'Ecuyer software generator for the current thread // - can be used as an alternative to several Random32 function calls function Lecuyer: PLecuyer; /// internal function used e.g. by TLecuyer.FillShort/FillShort31 procedure FillAnsiStringFromRandom(dest: PByteArray; size: PtrUInt); /// fast compute of some 32-bit random value, using the gsl_rng_taus2 generator // - this function will use well documented and proven Pierre L'Ecuyer software // generator - which happens to be faster (and safer) than RDRAND opcode (which // is used for seeding anyway) // - consider using TAesPrng.Main.Random32(), which offers cryptographic-level // randomness, but is twice slower (even with AES-NI) // - thread-safe and non-blocking function: each thread will maintain its own // TLecuyer table (note that RTL's system.Random function is not thread-safe) function Random32: cardinal; overload; /// fast compute of some 31-bit random value, using the gsl_rng_taus2 generator // - thread-safe function: each thread will maintain its own TLecuyer table function Random31: integer; /// fast compute of a 64-bit random value, using the gsl_rng_taus2 generator // - thread-safe function: each thread will maintain its own TLecuyer table function Random64: QWord; /// fast compute of bounded 32-bit random value, using the gsl_rng_taus2 generator // - calls internally the overloaded Random32 function, ensuring Random32(max) 0 // - warning: on x86, a should be <> b procedure bswap64array(a, b: PQWordArray; n: PtrInt); /// copy one memory buffer to another, swapping the bytes order // - used e.g. by TBigInt.Load/Save to follow DER big-endian encoding // - warning: src and dst should not overlap procedure MoveSwap(dst, src: PByte; n: PtrInt); /// low-level wrapper to add a callback to a dynamic list of events // - by default, you can assign only one callback to an Event: but by storing // it as a dynamic array of events, you can use this wrapper to add one callback // to this list of events // - if the event was already registered, do nothing (i.e. won't call it twice) // - since this function uses an unsafe typeless EventList parameter, you should // not use it in high-level code, but only as wrapper within dedicated methods // - will add Event to EventList[] unless Event is already registered // - is used e.g. by TJsonWriter as such: // ! ... // ! fEchos: array of TOnTextWriterEcho; // ! ... // ! procedure EchoAdd(const aEcho: TOnTextWriterEcho); // ! ... // ! procedure TEchoWriter.EchoAdd(const aEcho: TOnTextWriterEcho); // ! begin // ! MultiEventAdd(fEchos,TMethod(aEcho)); // ! end; // then callbacks are then executed as such: // ! if fEchos<>nil then // ! for i := 0 to length(fEchos) - 1 do // ! fEchos[i](self,fEchoBuf); // - use MultiEventRemove() to un-register a callback from the list function MultiEventAdd(var EventList; const Event: TMethod): boolean; /// low-level wrapper to remove a callback from a dynamic list of events // - by default, you can assign only one callback to an Event: but by storing // it as a dynamic array of events, you can use this wrapper to remove one // callback already registered by MultiEventAdd() to this list of events // - since this function uses an unsafe typeless EventList parameter, you should // not use it in high-level code, but only as wrapper within dedicated methods // - is used e.g. by TJsonWriter as such: // ! ... // ! fEchos: array of TOnTextWriterEcho; // ! ... // ! procedure EchoRemove(const aEcho: TOnTextWriterEcho); // ! ... // ! procedure TJsonWriter.EchoRemove(const aEcho: TOnTextWriterEcho); // ! begin // ! MultiEventRemove(fEchos,TMethod(aEcho)); // ! end; procedure MultiEventRemove(var EventList; const Event: TMethod); overload; /// low-level wrapper to remove a callback from a dynamic list of events // - same as the same overloaded procedure, but accepting an EventList[] index // to identify the Event to be suppressed procedure MultiEventRemove(var EventList; Index: integer); overload; /// low-level wrapper to check if a callback is in a dynamic list of events // - by default, you can assign only one callback to an Event: but by storing // it as a dynamic array of events, you can use this wrapper to check if // a callback has already been registered to this list of events // - used internally by MultiEventAdd() and MultiEventRemove() functions function MultiEventFind(const EventList; const Event: TMethod): PtrInt; /// low-level wrapper to add one or several callbacks from another list of events // - all events of the ToBeAddedList would be added to DestList // - the list is not checked for duplicates procedure MultiEventMerge(var DestList; const ToBeAddedList); /// compare two TMethod instances function EventEquals(const eventA, eventB): boolean; {$ifdef HASINLINE}inline;{$endif} { ************ Buffers (e.g. Hashing and SynLZ compression) Raw Functions } type /// implements a 4KB stack-based storage of some (UTF-8 or binary) content // - could be used e.g. to make a temporary copy when JSON is parsed in-place // - call one of the Init() overloaded methods, then Done to release its memory // - will avoid temporary memory allocation via the heap for up to 4KB of data // - all Init() methods will allocate 16 more bytes, for a trailing #0 and // to ensure our fast JSON parsing won't trigger any GPF (since it may read // up to 4 bytes ahead via its PInteger() trick) or any SSE4.2 function {$ifdef USERECORDWITHMETHODS} TSynTempBuffer = record {$else} TSynTempBuffer = object {$endif USERECORDWITHMETHODS} public /// the text/binary length, in bytes, excluding the trailing #0 len: PtrInt; /// where the text/binary is available (and any Source has been copied) // - equals nil if len=0 buf: pointer; /// default 4KB buffer allocated on stack - after the len/buf main fields // - 16 last bytes are reserved to prevent potential buffer overflow, // so usable length is 4080 bytes tmp: array[0..4095] of AnsiChar; /// initialize a temporary copy of the content supplied as RawByteString // - will also allocate and copy the ending #0 (even for binary) procedure Init(const Source: RawByteString); overload; /// initialize a temporary copy of the supplied text buffer, ending with #0 function Init(Source: PUtf8Char): PUtf8Char; overload; /// initialize a temporary copy of the supplied text buffer // - also include ending #0 at SourceLen position procedure Init(Source: pointer; SourceLen: PtrInt); overload; /// initialize a new temporary buffer of a given number of bytes // - also include ending #0 at SourceLen position function Init(SourceLen: PtrInt): pointer; overload; /// initialize a temporary buffer with the length of the internal stack function InitOnStack: pointer; {$ifdef HASINLINE}inline;{$endif} /// initialize the buffer returning the internal buffer size (4080 bytes) // - also set len to the internal buffer size // - could be used e.g. for an API call, first trying with plain temp.Init // and using temp.buf and temp.len safely in the call, only calling // temp.Init(expectedsize) if the API returns an insufficient buffer error function Init: integer; overload; {$ifdef HASINLINE}inline;{$endif} /// initialize a new temporary buffer of a given number of random bytes // - will fill the buffer via RandomBytes() call function InitRandom(RandomLen: integer): pointer; /// initialize a new temporary buffer filled with 32-bit integer increasing values function InitIncreasing(Count: PtrInt; Start: PtrInt = 0): PIntegerArray; /// initialize a new temporary buffer of a given number of zero bytes // - if ZeroLen=0, will initialize the whole tmp[] stack buffer to 0 function InitZero(ZeroLen: PtrInt): pointer; /// inlined wrapper around buf + len function BufEnd: pointer; {$ifdef HASINLINE}inline;{$endif} /// finalize the temporary storage procedure Done; overload; {$ifdef HASINLINE}inline;{$endif} /// finalize the temporary storage, and create a RawUtf8 string from it procedure Done(EndBuf: pointer; var Dest: RawUtf8); overload; end; PSynTempBuffer = ^TSynTempBuffer; /// logical OR of two memory buffers // - will perform on all buffer bytes: // ! Dest[i] := Dest[i] or Source[i]; procedure OrMemory(Dest, Source: PByteArray; size: PtrInt); {$ifdef HASINLINE}inline;{$endif} /// logical XOR of two memory buffers // - will perform on all buffer bytes: // ! Dest[i] := Dest[i] xor Source[i]; procedure XorMemory(Dest, Source: PByteArray; size: PtrInt); overload; {$ifdef HASINLINE}inline;{$endif} /// logical XOR of two memory buffers into a third // - will perform on all buffer bytes: // ! Dest[i] := Source1[i] xor Source2[i]; procedure XorMemory(Dest, Source1, Source2: PByteArray; size: PtrInt); overload; {$ifdef HASINLINE}inline;{$endif} /// logical AND of two memory buffers // - will perform on all buffer bytes: // ! Dest[i] := Dest[i] and Source[i]; procedure AndMemory(Dest, Source: PByteArray; size: PtrInt); {$ifdef HASINLINE}inline;{$endif} /// returns TRUE if all bytes equal zero function IsZero(P: pointer; Length: integer): boolean; overload; /// returns TRUE if all of a few bytes equal zero // - to be called instead of IsZero() e.g. for 1..8 bytes function IsZeroSmall(P: pointer; Length: PtrInt): boolean; {$ifdef HASINLINE}inline;{$endif} /// compute the line length from a size-delimited source array of chars // - will use fast SSE2 assembly on x86-64 CPU // - is likely to read some bytes after the TextEnd buffer, so GetLineSize() // from mormot.core.text may be preferred, e.g. on memory mapped files // - expects Text and TextEnd to be not nil - see GetLineSize() instead function BufferLineLength(Text, TextEnd: PUtf8Char): PtrInt; {$ifndef CPUX64}{$ifdef HASINLINE}inline;{$endif}{$endif} type TCrc32tab = array[0..7, byte] of cardinal; PCrc32tab = ^TCrc32tab; /// function prototype to be used for 32-bit hashing of an element // - it must return a cardinal hash, with as less collision as possible // - is the function signature of DefaultHasher and InterningHasher THasher = function(crc: cardinal; buf: PAnsiChar; len: cardinal): cardinal; /// function prototype to be used for 64-bit hashing of an element // - it must return a QWord hash, with as less collision as possible // - is the function signature of DefaultHasher64 THasher64 = function(crc: QWord; buf: PAnsiChar; len: cardinal): QWord; /// function prototype to be used for 128-bit hashing of an element // - the input hash buffer is used as seed, and contains the 128-bit result // - is the function signature of DefaultHasher128 THasher128 = procedure(hash: PHash128; buf: PAnsiChar; len: cardinal); var /// 8KB tables used by crc32cfast() function // - created with a polynom diverse from zlib's crc32() algorithm, but // compatible with SSE 4.2 crc32 instruction // - tables content is created from code in initialization section below // - will also be used internally by SymmetricEncrypt and // TSynUniqueIdentifierGenerator as 1KB master/reference key tables crc32ctab: TCrc32tab; /// 8KB tables used by crc32fast() function crc32tab: TCrc32tab; /// compute CRC32C checksum on the supplied buffer on processor-neutral code // - result is compatible with SSE 4.2 based hardware accelerated instruction // - will use fast x86/x64 asm or efficient pure pascal implementation on ARM // - result is not compatible with zlib's crc32() - not the same polynom // - crc32cfast() is 1.7 GB/s, crc32csse42() is 4.3 GB/s // - you should use crc32c() function instead of crc32cfast() or crc32csse42() function crc32cfast(crc: cardinal; buf: PAnsiChar; len: cardinal): cardinal; /// compute CRC32 checksum on the supplied buffer on processor-neutral code // - result is compatible with zlib's crc32() but not with crc32c/crc32cfast() function crc32fast(crc: cardinal; buf: PAnsiChar; len: cardinal): cardinal; /// compute CRC32C checksum on the supplied buffer using inlined code // - if the compiler supports inlining, will compute a slow but safe crc32c // checksum of the binary buffer, without calling the main crc32c() function // - may be used e.g. to identify patched executable at runtime, for a licensing // protection system, or if you don't want to pollute the CPU L1 cache with // crc32cfast() bigger lookup tables function crc32cinlined(crc: cardinal; buf: PAnsiChar; len: cardinal): cardinal; {$ifdef HASINLINE}inline;{$endif} /// compute CRC64C checksum on the supplied buffer, cascading two crc32c // - will use SSE 4.2 or ARMv8 hardware accelerated instruction, if available // - will combine two crc32c() calls into a single Int64 result // - by design, such combined hashes cannot be cascaded function crc64c(buf: PAnsiChar; len: cardinal): Int64; /// expand a CRC32C checksum on the supplied buffer for 64-bit hashing // - will use SSE 4.2 or ARMv8 hardware accelerated instruction, if available // - is the default implementation of DefaultHasher64 function crc32ctwice(seed: QWord; buf: PAnsiChar; len: cardinal): QWord; /// compute CRC63C checksum on the supplied buffer, cascading two crc32c // - similar to crc64c, but with 63-bit, so no negative value: may be used // safely e.g. as mORMot's TID source // - will use SSE 4.2 or ARMv8 hardware accelerated instruction, if available // - will combine two crc32c() calls into an unsigned 63-bit Int64 result // - by design, such combined hashes cannot be cascaded function crc63c(buf: PAnsiChar; len: cardinal): Int64; /// compute a 128-bit checksum on the supplied buffer, cascading two crc32c // - will use SSE 4.2 or ARMv8 hardware accelerated instruction, if available // - will combine two crc32c() calls into a single TAesBlock result // - by design, such combined hashes cannot be cascaded procedure crc128c(buf: PAnsiChar; len: cardinal; out crc: THash128); /// compute a 256-bit checksum on the supplied buffer using crc32c // - will use SSE 4.2 or ARMv8 hardware accelerated instruction, if available // - will combine two crc32c() calls into a single THash256 result // - by design, such combined hashes cannot be cascaded procedure crc256c(buf: PAnsiChar; len: cardinal; out crc: THash256); /// pure pascal function implementing crc32cBy4() function crc32cBy4fast(crc, value: cardinal): cardinal; /// compute a proprietary 128-bit CRC of 128-bit binary buffers // - to be used for regression tests only: crcblocks will use the fastest // implementation available on the current CPU (e.g. with SSE 4.2 or ARMv8) procedure crcblocksfast(crc128, data128: PBlock128; count: integer); /// computation of our 128-bit CRC of a 128-bit binary buffer without SSE4.2 // - to be used for regression tests only: crcblock will use the fastest // implementation available on the current CPU (e.g. with SSE 4.2 or ARMv8) procedure crcblockfast(crc128, data128: PBlock128); /// compute a 128-bit CRC of any binary buffers // - combine crcblocks() with 4 parallel crc32c() for 1..15 trailing bytes procedure crc32c128(hash: PHash128; buf: PAnsiChar; len: cardinal); var /// compute CRC32C checksum on the supplied buffer // - result is not compatible with zlib's crc32() - Intel/SCSI CRC32C has not // same polynom - but will use the fastest mean available, e.g. SSE 4.2 or ARMv8, // achieve up to 16GB/s with the optimized implementation from mormot.crypt.core // - you should use this function instead of crc32cfast() or crc32csse42() crc32c: THasher = crc32cfast; /// compute CRC32C checksum on one 32-bit unsigned integer // - can be used instead of crc32c() for inlined process during data acquisition // - doesn't make "crc := not crc" before and after the computation: caller has // to start with "crc := cardinal(not 0)" and make "crc := not crc" at the end, // to compute the very same hash value than regular crc32c() // - this variable will use the fastest mean available, e.g. SSE 4.2 or ARMv8 crc32cBy4: function(crc, value: cardinal): cardinal = crc32cBy4fast; /// compute a proprietary 128-bit CRC of a 128-bit binary buffer // - apply four crc32c() calls on the 128-bit input chunk, into a 128-bit crc // - its output won't match crc128c() value, which works on 8-bit input // - will use SSE 4.2 or ARMv8 hardware accelerated instruction, if available // - is used e.g. by mormot.crypt.core's TAesCfc/TAesOfc/TAesCtc to // check for data integrity crcblock: procedure(crc128, data128: PBlock128) = crcblockfast; /// compute a proprietary 128-bit CRC of 128-bit binary buffers // - apply four crc32c() calls on the 128-bit input chunks, into a 128-bit crc // - its output won't match crc128c() value, which works on 8-bit input // - will use SSE 4.2 or ARMv8 hardware accelerated instruction, if available // - is used e.g. by crc32c128 or mormot.crypt.ecc's TEcdheProtocol.ComputeMAC // for macCrc128c or TAesAbstractAead.MacCheckError crcblocks: procedure(crc128, data128: PBlock128; count: integer) = crcblocksfast; /// compute CRC32 checksum on the supplied buffer // - mormot.lib.z.pas will replace with its official (may be faster) version crc32: THasher = crc32fast; /// compute ADLER32 checksum on the supplied buffer // - is only available if mormot.lib.z.pas unit is included in the project adler32: THasher; /// compute CRC16-CCITT checkum on the supplied buffer // - i.e. 16-bit CRC-CCITT, with polynomial x^16 + x^12 + x^5 + 1 ($1021) // and $ffff as initial value // - this version is not optimized for speed, but for correctness function crc16(Data: PAnsiChar; Len: integer): cardinal; // our custom efficient 32-bit hash/checksum function // - a Fletcher-like checksum algorithm, not a hash function: has less colisions // than Adler32 for short strings, but more than xxhash32 or crc32/crc32c // - written in simple plain pascal, with no L1 CPU cache pollution, but we // also provide optimized x86/x64 assembly versions, since the algorithm is used // heavily e.g. for TDynArray binary serialization, TRestStorageInMemory // binary persistence, or CompressSynLZ/StreamSynLZ/FileSynLZ // - some numbers on Linux x86_64: // $ 2500 xxhash32 in 1.34ms i.e. 1861504/s or 3.8 GB/s // $ 2500 crc32c in 943us i.e. 2651113/s or 5.5 GB/s (SSE4.2 disabled) // $ 2500 hash32 in 707us i.e. 3536067/s or 7.3 GB/s // $ 2500 crc32c in 387us i.e. 6459948/s or 13.4 GB/s (SSE4.2 enabled) function Hash32(Data: PCardinalArray; Len: integer): cardinal; overload; // our custom efficient 32-bit hash/checksum function // - a Fletcher-like checksum algorithm, not a hash function: has less colisions // than Adler32 for short strings, but more than xxhash32 or crc32/crc32c // - overloaded function using RawByteString for binary content hashing, // whatever the codepage is function Hash32(const Text: RawByteString): cardinal; overload; {$ifdef HASINLINE}inline;{$endif} /// standard Kernighan & Ritchie hash from "The C programming Language", 3rd edition // - simple and efficient code, but too much collisions for THasher // - kr32() is 898.8 MB/s - crc32cfast() 1.7 GB/s, crc32csse42() 4.3 GB/s function kr32(crc: cardinal; buf: PAnsiChar; len: PtrInt): cardinal; /// simple FNV-1a hashing function // - when run over our regression suite, is similar to crc32c() about collisions, // and 4 times better than kr32(), but also slower than the others // - fnv32() is 715.5 MB/s - kr32() 898.8 MB/s // - this hash function should not be usefull, unless you need several hashing // algorithms at once (e.g. if crc32c with diverse seeds is not enough) function fnv32(crc: cardinal; buf: PAnsiChar; len: PtrInt): cardinal; /// perform very fast xxHash hashing in 32-bit mode // - will use optimized asm for x86/x64, or a pascal version on other CPUs function xxHash32(crc: cardinal; P: PAnsiChar; len: cardinal): cardinal; /// shuffle a 32-bit value using the last stage of xxHash32 algorithm // - is a cascade of binary shifts and multiplications by prime numbers // - see also (c * KNUTH_HASH32_MUL) shr (32 - bits) as weaker alternative function xxHash32Mixup(crc: cardinal): cardinal; {$ifdef HASINLINE}inline;{$endif} const /// Knuth's magic number for hashing a 32-bit value, using the golden ratio // - then use the result high bits, i.e. via "shr" not via "and" // - for instance, mormot.core.log uses it to hash the TThreadID: // $ hash := cardinal(cardinal(id) * KNUTH_HASH32_MUL) shr (32 - MAXLOGTHREADBITS); KNUTH_HASH32_MUL = $9E3779B1; /// Knuth's magic number for hashing a 64-bit value, using the golden ratio KNUTH_HASH64_MUL = $9E3779B97F4A7C15; /// Knuth's magic number for hashing a PtrUInt, using the golden ratio {$ifdef CPU32} KNUTH_HASHPTR_MUL = KNUTH_HASH32_MUL; KNUTH_HASHPTR_SHR = 32; {$else} KNUTH_HASHPTR_MUL = KNUTH_HASH64_MUL; KNUTH_HASHPTR_SHR = 64; {$endif CPU32} var /// the 32-bit default hasher used by TDynArrayHashed // - set to crc32csse42() if SSE4.2 or ARMv8 are available on this CPU, // or fallback to xxHash32() which is faster than crc32cfast() e.g. on ARM // - mormot.crypt.core may assign safer and faster AesNiHash32() if available // - so the hash value may change on another computer or after program restart DefaultHasher: THasher = xxHash32; /// the 32-bit hash function used by TRawUtf8Interning // - set to crc32csse42() if SSE4.2 or ARMv8 are available on this CPU, // or fallback to xxHash32() which performs better than crc32cfast() // - mormot.crypt.core may assign safer and faster AesNiHash32() if available // - so the hash value may change on another computer or after program restart InterningHasher: THasher = xxHash32; /// a 64-bit hasher function // - crc32cTwice() by default, but mormot.crypt.core may assign AesNiHash64() // - so the hash value may change on another computer or after program restart DefaultHasher64: THasher64 = crc32cTwice; /// a 128-bit hasher function // - crc32c128() by default, but mormot.crypt.core may assign AesNiHash128() // - so the hash value may change on another computer or after program restart DefaultHasher128: THasher128 = crc32c128; /// compute a 32-bit hash of any string using DefaultHasher() // - so the hash value may change on another computer or after program restart function DefaultHash(const s: RawByteString): cardinal; overload; {$ifdef HASINLINE}inline;{$endif} /// compute a 32-bit hash of any array of bytes using DefaultHasher() // - so the hash value may change on another computer or after program restart function DefaultHash(const b: TBytes): cardinal; overload; {$ifdef HASINLINE}inline;{$endif} /// compute a 32-bit hash of any string using the CRC32C checksum // - the returned hash value will be stable on all platforms, and use HW opcodes // if available on the current CPU function crc32cHash(const s: RawByteString): cardinal; overload; {$ifdef HASINLINE}inline;{$endif} /// compute a 32-bit hash of any array of bytes using the CRC32C checksum // - the returned hash value will be stable on all platforms, and use HW opcodes // if available on the current CPU function crc32cHash(const b: TBytes): cardinal; overload; {$ifdef HASINLINE}inline;{$endif} /// combine/reduce a 128-bit hash into a 64-bit hash // - e.g. from non cryptographic 128-bit hashers with linked lower/higher 64-bit function Hash128To64(const b: THash128): QWord; {$ifdef HASINLINE}inline;{$endif} /// get maximum possible (worse) SynLZ compressed size function SynLZcompressdestlen(in_len: integer): integer; {$ifdef HASINLINE}inline;{$endif} /// get exact uncompressed size from SynLZ-compressed buffer (to reserve memory, e.g.) function SynLZdecompressdestlen(in_p: PAnsiChar): integer; /// raw SynLZ compression algorithm implemented in pascal // - you should rather call SynLZcompress1() which is likely to be much faster function SynLZcompress1pas(src: PAnsiChar; size: integer; dst: PAnsiChar): integer; /// raw SynLZ decompression algorithm implemented in pascal // - you should rather call SynLZdecompress1() which is likely to be much faster function SynLZdecompress1pas(src: PAnsiChar; size: integer; dst: PAnsiChar): integer; /// SynLZ decompression algorithm with memory boundaries check // - this function is slower, but will allow to uncompress only the start // of the content (e.g. to read some metadata header) // - it will also check for dst buffer overflow, so will be more secure than // other functions, which expect the content to be verified (e.g. via CRC) function SynLZdecompress1partial(src: PAnsiChar; size: integer; dst: PAnsiChar; maxDst: integer): integer; /// raw SynLZ compression algorithm // - includes optimized x86/x64 asm version on Intel/AMD // - just redirects to SynLZcompress1pas on other CPUs // - note that SynLZ is not very good at compressing a lot of zeros: it excels // with somewhat already pre-encoded data like text, JSON or our mormot.core.data // binary serialization function SynLZcompress1(src: PAnsiChar; size: integer; dst: PAnsiChar): integer; {$ifndef CPUINTEL} inline; {$endif} /// raw SynLZ decompression algorithm // - includes optimized x86/x64 asm version on Intel/AMD // - just redirects to SynLZcompress1pas on other CPUs function SynLZdecompress1(src: PAnsiChar; size: integer; dst: PAnsiChar): integer; {$ifndef CPUINTEL} inline; {$endif} /// compress a data content using the SynLZ algorithm // - as expected by THttpSocket.RegisterCompress // - will return 'synlz' as ACCEPT-ENCODING: header parameter // - will store a hash of both compressed and uncompressed stream: if the // data is corrupted during transmission, will instantly return '' function CompressSynLZ(var Data: RawByteString; Compress: boolean): RawUtf8; /// return the Hash32() 32-bit CRC of CompressSynLZ() uncompressed data // - will first check the CRC of the supplied compressed Data // - returns 0 if the CRC of the compressed Data is not correct function CompressSynLZGetHash32(const Data: RawByteString): cardinal; /// simple Run-Length-Encoding compression of a memory buffer // - SynLZ is not good with input of a lot of redundant bytes, e.g. chunks of // zeros: you could pre-process RleCompress/RleUnCompress such data before SynLZ // - see AlgoRleLZ as such a RLE + SynLZ algorithm // - returns the number of bytes written to dst, or -1 on dstsize overflow function RleCompress(src, dst: PByteArray; srcsize, dstsize: PtrUInt): PtrInt; /// simple Run-Length-Encoding uncompression of a memory buffer // - SynLZ is not good with input of a lot of redundant bytes, e.g. chunks of // zeros: you could pre-process RleCompress/RleUnCompress such data before SynLZ // - see AlgoRleLZ as such a RLE + SynLZ algorithm function RleUnCompress(src, dst: PByteArray; size: PtrUInt): PtrUInt; /// partial Run-Length-Encoding uncompression of a memory buffer function RleUnCompressPartial(src, dst: PByteArray; size, max: PtrUInt): PtrUInt; /// internal hash table adjustment as called from TDynArrayHasher.HashDelete // - decrement any integer greater or equal to a deleted value // - brute force O(n) indexes fix after deletion (much faster than full ReHash) // - we offer very optimized SSE2 and AVX2 versions on x86_64 - therefore is // defined in this unit to put this asm code in mormot.core.base.asmx64.inc procedure DynArrayHashTableAdjust(P: PIntegerArray; deleted: integer; count: PtrInt); /// DynArrayHashTableAdjust() version for 16-bit HashTable[] - SSE2 asm on x86_64 procedure DynArrayHashTableAdjust16(P: PWordArray; deleted: cardinal; count: PtrInt); { ************ Efficient Variant Values Conversion } type PVarType = ^TVarType; const /// unsigned 64bit integer variant type // - currently called varUInt64 in Delphi (not defined in older versions), // and varQWord in FPC varWord64 = 21; /// map the Windows VT_INT extended VARENUM, i.e. a 32-bit signed integer // - also detected and handled by VariantToInteger/VariantToInt64 varOleInt = 22; /// map the Windows VT_UINT extended VARENUM, i.e. a 32-bit unsigned integer // - also detected and handled by VariantToInteger/VariantToInt64 varOleUInt = 23; /// map the Windows VT_LPSTR extended VARENUM, i.e. a PAnsiChar // - also detected and handled by VariantToUtf8 varOlePAnsiChar = 30; /// map the Windows VT_LPWSTR extended VARENUM, i.e. a PWideChar // - also detected and handled by VariantToUtf8 varOlePWideChar = 31; /// map the Windows VT_FILETIME extended VARENUM, i.e. a 64-bit TFileTime // - also detected and handled by VariantToDateTime varOleFileTime = 64; /// map the Windows VT_CLSID extended VARENUM, i.e. a by-reference PGuid varOleClsid = 72; varVariantByRef = varVariant or varByRef; varStringByRef = varString or varByRef; varOleStrByRef = varOleStr or varByRef; /// this variant type will map the current SynUnicode type // - depending on the compiler version {$ifdef HASVARUSTRING} varSynUnicode = varUString; varUStringByRef = varUString or varByRef; {$else} varSynUnicode = varOleStr; {$endif HASVARUSTRING} /// this variant type will map the current string type // - depending on the compiler string definition (UnicodeString or AnsiString) {$ifdef UNICODE} varNativeString = varUString; {$else} varNativeString = varString; {$endif UNICODE} {$ifdef ISDELPHI} CFirstUserType = $10F; {$endif ISDELPHI} /// those TVarData.VType values are meant to be direct values VTYPE_SIMPLE = [varEmpty..varDate, varBoolean, varShortInt..varWord64, {$ifdef OSWINDOWS} varOleInt, varOleUInt, varOlePAnsiChar, varOlePWideChar, varOleFileTime, {$endif OSWINDOWS} varUnknown]; /// bitmask used by our inlined VarClear() to avoid unneeded VarClearProc() VTYPE_STATIC = $BFE8; /// a slightly faster alternative to Variants.Null function with TVarData NullVarData: TVarData = (VType: varNull{%H-}); FalseVarData: TVarData = (VType: varBoolean{%H-}); TrueVarData: TVarData = (VType: varBoolean; VInteger: {%H-}1); var /// a slightly faster alternative to Variants.Null function Null: variant absolute NullVarData; /// a slightly faster alternative to false constant when assigned to a variant VarFalse: variant absolute FalseVarData; /// a slightly faster alternative to true constant when assigned to a variant VarTrue: variant absolute TrueVarData; {$ifdef HASINLINE} /// overloaded function which can be properly inlined to clear a variant procedure VarClear(var v: variant); inline; {$endif HASINLINE} /// overloaded function which can be properly inlined to clear a variant procedure VarClearAndSetType(var v: variant; vtype: integer); {$ifdef HASINLINE}inline;{$endif} /// internal efficient wrapper of VarClear() + set VType=varString and VAny=nil // - used e.g. by RawUtf8ToVariant() functions // - could also be used as a faster alternative to Value := '' procedure ClearVariantForString(var Value: variant); {$ifdef HASINLINE}inline;{$endif} /// same as Value := Null, but slightly faster procedure SetVariantNull(var Value: variant); {$ifdef HASINLINE}inline;{$endif} /// convert a raw binary buffer into a variant RawByteString varString // - you can then use VariantToRawByteString() to retrieve the binary content procedure RawByteStringToVariant(Data: PByte; DataLen: integer; var Value: variant); overload; /// convert a RawByteString content into a variant varString // - you can then use VariantToRawByteString() to retrieve the binary content procedure RawByteStringToVariant(const Data: RawByteString; var Value: variant); overload; /// convert back a RawByteString from a variant // - the supplied variant should have been created via a RawByteStringToVariant() // function call procedure VariantToRawByteString(const Value: variant; var Dest: RawByteString); /// get the root PVarData of a variant, redirecting any varByRef // - if result^.VPointer=nil, returns varEmpty function VarDataFromVariant(const Value: variant): PVarData; {$ifdef HASINLINE}inline;{$endif} /// same as VarIsEmpty(V) or VarIsNull(V), but faster // - we also discovered some issues with FPC's Variants unit, so this function // may be used even in end-user cross-compiler code function VarIsEmptyOrNull(const V: Variant): boolean; {$ifdef HASINLINE}inline;{$endif} /// same as VarIsEmpty(PVariant(V)^) or VarIsNull(PVariant(V)^), but faster // - we also discovered some issues with FPC's Variants unit, so this function // may be used even in end-user cross-compiler code function VarDataIsEmptyOrNull(VarData: pointer): boolean; {$ifdef HASINLINE}inline;{$endif} /// same as Dest := TVarData(Source) for simple values // - will return TRUE for all simple values after varByRef unreference, and // copying the unreferenced Source value into Dest raw storage // - will return FALSE for not varByRef values, or complex values (e.g. string) function SetVariantUnRefSimpleValue(const Source: variant; var Dest: TVarData): boolean; {$ifdef HASINLINE}inline;{$endif} /// convert any numerical Variant into a 32-bit integer // - it will expect true numerical Variant and won't convert any string nor // floating-pointer Variant, which will return FALSE and won't change the // Value variable content function VariantToInteger(const V: Variant; var Value: integer): boolean; /// convert any numerical Variant into a 64-bit integer // - it will expect true numerical Variant and won't convert any string nor // floating-pointer Variant, which will return FALSE and won't change the // Value variable content function VariantToInt64(const V: Variant; var Value: Int64): boolean; /// convert any numerical Variant into a 64-bit integer // - it will expect true numerical Variant and won't convert any string nor // floating-pointer Variant, which will return the supplied DefaultValue function VariantToInt64Def(const V: Variant; DefaultValue: Int64): Int64; /// convert any numerical Variant into a floating point value function VariantToDouble(const V: Variant; var Value: double): boolean; /// convert any numerical Variant into a floating point value function VariantToDoubleDef(const V: Variant; const default: double = 0): double; /// convert any numerical Variant into a fixed decimals floating point value function VariantToCurrency(const V: Variant; var Value: currency): boolean; /// convert any numerical Variant into a boolean value // - text content will return true after case-sensitive 'true' comparison function VariantToBoolean(const V: Variant; var Value: boolean): boolean; /// convert any numerical Variant into an integer // - it will expect true numerical Variant and won't convert any string nor // floating-pointer Variant, which will return the supplied DefaultValue function VariantToIntegerDef(const V: Variant; DefaultValue: integer): integer; overload; /// convert an UTF-8 encoded text buffer into a variant RawUtf8 varString procedure RawUtf8ToVariant(Txt: PUtf8Char; TxtLen: integer; var Value: variant); overload; /// convert an UTF-8 encoded string into a variant RawUtf8 varString procedure RawUtf8ToVariant(const Txt: RawUtf8; var Value: variant); overload; /// convert an UTF-8 encoded string into a variant RawUtf8 varString function RawUtf8ToVariant(const Txt: RawUtf8): variant; overload; {$ifdef HASINLINE}inline;{$endif} /// convert a Variant varString value into RawUtf8 encoded String // - works as the exact reverse of RawUtf8ToVariant() function // - non varString variants (e.g. UnicodeString, WideString, numbers, empty and // null) will be returned as '' // - use VariantToUtf8() instead if you need to convert numbers or other strings // - use VariantSaveJson() instead if you need a conversion to JSON with // custom parameters procedure VariantStringToUtf8(const V: Variant; var result: RawUtf8); overload; /// convert Variant string values into RawUtf8 encoded String // - works as the exact reverse of RawUtf8ToVariant() function // - non varString variants (e.g. UnicodeString, WideString, numbers, empty and // null) will be returned as '' function VariantStringToUtf8(const V: Variant): RawUtf8; overload; var /// efficient finalization of successive variant items from a (dynamic) array // - this unit will include a basic version calling VarClear() // - mormot.core.variants will assign a more efficient implementation VariantClearSeveral: procedure(V: PVarData; n: integer); /// compare two variant/TVarData values, with or without case sensitivity // - this unit registers the basic VariantCompSimple() case-sensitive comparer // - mormot.core.variants will assign the much better FastVarDataComp() // - called e.g. by SortDynArrayVariant/SortDynArrayVariantI functions SortDynArrayVariantComp: function( const A, B: TVarData; caseInsensitive: boolean): integer; /// basic default case-sensitive variant comparison function // - try as VariantToInt64/VariantToDouble, then RTL VarCompareValue() function VariantCompSimple(const A, B: variant): integer; { ************ Sorting/Comparison Functions } type /// function prototype to be used for TDynArray Sort and Find method // - common functions exist for base types: see e.g. SortDynArrayBoolean, // SortDynArrayByte, SortDynArrayWord, SortDynArrayInteger, SortDynArrayCardinal, // SortDynArrayInt64, SortDynArrayQWord, SordDynArraySingle, SortDynArrayDouble, // SortDynArrayAnsiString, SortDynArrayAnsiStringI, SortDynArrayUnicodeString, // SortDynArrayUnicodeStringI, SortDynArrayString, SortDynArrayStringI // - any custom type (even records) can be compared then sort by defining // such a custom function // - must return 0 if A=B, -1 if AB // - simple types are compared within this unit (with proper optimized asm // if possible), whereas more complex types are implemented in other units - // e.g. SortDynArrayVariant/SortDynArrayVariantI are in mormot.core.variants // and SortDynArrayPUtf8CharI/SortDynArrayStringI in mormot.core.text TDynArraySortCompare = function(const A, B): integer; /// the recognized operators for comparison functions results match TCompareOperator = ( coEqualTo, coNotEqualTo, coLessThan, coLessThanOrEqualTo, coGreaterThan, coGreaterThanOrEqualTo); /// fast search if a comparison function result (<0,0,>0) match an operator function SortMatch(CompareResult: integer; CompareOperator: TCompareOperator): boolean; {$ifdef HASINLINE} inline; {$endif} /// compare two "array of boolean" elements function SortDynArrayBoolean(const A, B): integer; /// compare two "array of shortint" elements function SortDynArrayShortint(const A, B): integer; /// compare two "array of byte" elements function SortDynArrayByte(const A, B): integer; /// compare two "array of smallint" elements function SortDynArraySmallint(const A, B): integer; /// compare two "array of word" elements function SortDynArrayWord(const A, B): integer; /// compare two "array of integer" elements function SortDynArrayInteger(const A, B): integer; /// compare two "array of cardinal" elements function SortDynArrayCardinal(const A, B): integer; /// compare two "array of Int64" or "array of Currency" elements function SortDynArrayInt64(const A, B): integer; /// compare two "array of QWord" elements // - note that QWord(A)>QWord(B) is wrong on older versions of Delphi, so you // should better use this function or CompareQWord() to properly compare two // QWord values over CPUX86 function SortDynArrayQWord(const A, B): integer; /// compare two "array of THash128" elements function SortDynArray128(const A, B): integer; /// compare two "array of THash256" elements function SortDynArray256(const A, B): integer; /// compare two "array of THash512" elements function SortDynArray512(const A, B): integer; /// compare two "array of TObject/pointer" elements function SortDynArrayPointer(const A, B): integer; /// compare two "array of single" elements function SortDynArraySingle(const A, B): integer; /// compare two "array of double" elements function SortDynArrayDouble(const A, B): integer; /// compare two "array of extended" elements function SortDynArrayExtended(const A, B): integer; /// compare two "array of AnsiString" elements, with case sensitivity // - on Intel/AMD will use efficient i386/x86_64 assembly using length // - on other CPU, will redirect to inlined StrComp() using #0 trailing char function SortDynArrayAnsiString(const A, B): integer; /// compare two "array of RawByteString" elements, with case sensitivity // - can't use StrComp() or similar functions since RawByteString may contain #0 // - on Intel/AMD, the more efficient SortDynArrayAnsiString asm is used instead {$ifdef CPUINTEL} var SortDynArrayRawByteString: TDynArraySortCompare = SortDynArrayAnsiString; {$else} function SortDynArrayRawByteString(const A, B): integer; {$endif CPUINTEL} /// compare two "array of PUtf8Char/PAnsiChar" elements, with case sensitivity function SortDynArrayPUtf8Char(const A, B): integer; /// compare two "array of WideString/UnicodeString" elements, with case sensitivity function SortDynArrayUnicodeString(const A, B): integer; /// compare two "array of RTL string" elements, with case sensitivity // - the expected string type is the RTL string function SortDynArrayString(const A, B): integer; /// compare two "array of shortstring" elements, with case sensitivity function SortDynArrayShortString(const A, B): integer; /// compare two "array of variant" elements, with case sensitivity // - just a wrapper around SortDynArrayVariantComp(A,B,false) function SortDynArrayVariant(const A, B): integer; /// compare two "array of variant" elements, with no case sensitivity // - just a wrapper around SortDynArrayVariantComp(A,B,true) function SortDynArrayVariantI(const A, B): integer; /// low-level inlined function for exchanging two pointers // - used e.g. during sorting process procedure ExchgPointer(n1, n2: PPointer); {$ifdef HASINLINE}inline;{$endif} /// low-level inlined function for exchanging two sets of pointers // - used e.g. during sorting process procedure ExchgPointers(n1, n2: PPointer; count: PtrInt); {$ifdef HASINLINE}inline;{$endif} /// low-level inlined function for exchanging two variants // - used e.g. during sorting process procedure ExchgVariant(v1, v2: PPtrIntArray); {$ifdef CPU64} inline;{$endif} /// low-level inlined function for exchanging two memory buffers // - used e.g. during sorting process procedure Exchg(P1, P2: PAnsiChar; count: PtrInt); {$ifdef HASINLINE}inline;{$endif} { ************ Some Convenient TStream descendants and File access functions } type /// a dynamic array of TStream instances TStreamDynArray = array of TStream; {$M+} /// TStream with a protected fPosition field TStreamWithPosition = class(TStream) protected fPosition: Int64; {$ifdef FPC} function GetPosition: Int64; override; {$endif FPC} public /// change the current Read/Write position, within current GetSize function Seek(const Offset: Int64; Origin: TSeekOrigin): Int64; override; /// call the 64-bit Seek() overload function Seek(Offset: Longint; Origin: Word): Longint; override; end; {$M-} /// TStream with two protected fPosition/fSize fields TStreamWithPositionAndSize = class(TStreamWithPosition) protected fSize: Int64; function GetSize: Int64; override; end; /// TStream using a RawByteString as internal storage // - default TStringStream uses UTF-16 WideChars since Delphi 2009, so it is // not compatible with previous versions or FPC, and it makes more sense to // work with RawByteString/RawUtf8 in our UTF-8 oriented framework // - just like TStringStream, is designed for appending data, not modifying // in-place, as requested e.g. by TJsonWriter or TBufferWriter classes TRawByteStringStream = class(TStreamWithPosition) protected fDataString: RawByteString; function GetSize: Int64; override; procedure SetSize(NewSize: Longint); override; public /// initialize the storage, optionally with some RawByteString content // - to be used for Read() from this memory buffer constructor Create(const aString: RawByteString); overload; /// read some bytes from the internal storage // - returns the number of bytes filled into Buffer (<=Count) function Read(var Buffer; Count: Longint): Longint; override; /// append some data to the buffer // - will resize the buffer, i.e. will replace the end of the string from // the current position with the supplied data function Write(const Buffer; Count: Longint): Longint; override; /// retrieve the stored content from a given position, as UTF-8 text // - warning: may directly return DataString and reset its value to '' procedure GetAsText(StartPos, Len: PtrInt; var Text: RawUtf8); /// reset the internal DataString content and the current position procedure Clear; {$ifdef HASINLINE}inline;{$endif} /// direct low-level access to the internal RawByteString storage property DataString: RawByteString read fDataString write fDataString; end; /// TStream pointing to some existing in-memory data, for instance UTF-8 text // - warning: there is no local copy of the supplied content: the // source data must be available during all the TSynMemoryStream usage TSynMemoryStream = class(TCustomMemoryStream) public /// create a TStream with the supplied text data // - warning: there is no local copy of the supplied content: the aText // variable must be available during all the TSynMemoryStream usage: // don't release aText before calling TSynMemoryStream.Free // - aText can be on any AnsiString format, e.g. RawUtf8 or RawByteString constructor Create(const aText: RawByteString); overload; /// create a TStream with the supplied data buffer // - warning: there is no local copy of the supplied content: the // Data/DataLen buffer must be available during all the TSynMemoryStream usage: // don't release the source Data before calling TSynMemoryStream.Free constructor Create(Data: pointer; DataLen: PtrInt); overload; /// this TStream is read-only: calling this method will raise an exception function Write(const Buffer; Count: Longint): Longint; override; end; /// raise a EStreamError exception - e.g. from TSynMemoryStream.Write function RaiseStreamError(Caller: TObject; const Context: shortstring): PtrInt; { ************ Raw Shared Constants / Types Definitions } { some types defined here, but implemented in mormot.core.datetime or mormot.core.log, so that they may be used and identified by mormot.core.rtti or mormot.core.os } type /// the available logging events, as handled by mormot.core.log // - defined in mormot.core.base so that it may be used by the core units, // even if mormot.core.log is not explicitely linked // - limited to 32 items, to efficiently fit in a 32-bit set // - sllInfo will log general information events // - sllDebug will log detailed debugging information // - sllTrace will log low-level step by step debugging information // - sllWarning will log unexpected values (not an error) // - sllError will log errors // - sllEnter will log every method start // - sllLeave will log every method exit // - sllLastError will log the GetLastError OS message // - sllException will log all exception raised - available since Windows XP // - sllExceptionOS will log all OS low-level exceptions (EDivByZero, // ERangeError, EAccessViolation...) // - sllMemory will log memory statistics (in MB units) // - sllStackTrace will log caller's stack trace (it's by default part of // TSynLogFamily.LevelStackTrace like sllError, sllException, sllExceptionOS, // sllLastError and sllFail) // - sllFail was defined for TSynTestsLogged.Failed method, and can be used // to log some customer-side assertions (may be notifications, not errors) // - sllSQL is dedicated to trace the SQL statements // - sllCache should be used to trace the internal caching mechanism // - sllResult could trace the SQL results, JSON encoded // - sllDB is dedicated to trace low-level database engine features // - sllHTTP could be used to trace HTTP process // - sllClient/sllServer could be used to trace some Client or Server process // - sllServiceCall/sllServiceReturn to trace some remote service or library // - sllUserAuth to trace user authentication (e.g. for individual requests) // - sllCustom* items can be used for any purpose // - sllNewRun will be written when a process opens a rotated log // - sllDDDError will log any DDD-related low-level error information // - sllDDDInfo will log any DDD-related low-level debugging information // - sllMonitoring will log the statistics information (if available), // or may be used for real-time chat among connected people to ToolsAdmin TSynLogLevel = ( sllNone, sllInfo, sllDebug, sllTrace, sllWarning, sllError, sllEnter, sllLeave, sllLastError, sllException, sllExceptionOS, sllMemory, sllStackTrace, sllFail, sllSQL, sllCache, sllResult, sllDB, sllHTTP, sllClient, sllServer, sllServiceCall, sllServiceReturn, sllUserAuth, sllCustom1, sllCustom2, sllCustom3, sllCustom4, sllNewRun, sllDDDError, sllDDDInfo, sllMonitoring); /// used to define a set of logging level abilities // - i.e. a combination of none or several logging event // - e.g. use LOG_VERBOSE constant to log all events, or LOG_STACKTRACE // to log all errors and exceptions TSynLogLevels = set of TSynLogLevel; /// a dynamic array of logging event levels TSynLogLevelDynArray = array of TSynLogLevel; /// callback definition used to abstractly log some events // - defined as TMethod to avoid dependency with the mormot.core.log unit // - match class procedure TSynLog.DoLog // - used e.g. by global variables like WindowsServiceLog in mormot.core.os // or TCrtSocket.OnLog in mormot.net.sock TSynLogProc = procedure(Level: TSynLogLevel; const Fmt: RawUtf8; const Args: array of const; Instance: TObject = nil) of object; {$ifndef PUREMORMOT2} TSynLogInfo = TSynLogLevel; TSynLogInfos = TSynLogLevels; TSynLogInfoDynArray = TSynLogLevelDynArray; {$endif PUREMORMOT2} type /// fast bit-encoded date and time value // - see TTimeLog helper functions and types in mormot.core.datetime // - faster than Iso-8601 text and TDateTime, e.g. can be used as published // property field in mORMot's TOrm (see also TModTime and TCreateTime) // - use internally for computation an abstract "year" of 16 months of 32 days // of 32 hours of 64 minutes of 64 seconds - same as Iso8601ToTimeLog() // - use TimeLogFromDateTime/TimeLogToDateTime/TimeLogNow functions, or // type-cast any TTimeLog value with the TTimeLogBits memory structure for // direct access to its bit-oriented content (or via PTimeLogBits pointer) // - since TTimeLog type is bit-oriented, you can't just add or substract two // TTimeLog values when doing date/time computation: use a TDateTime temporary // conversion in such case: // ! aTimestamp := TimeLogFromDateTime(IncDay(TimeLogToDateTime(aTimestamp))); TTimeLog = type Int64; /// dynamic array of TTimeLog // - recognized e.g. by TDynArray JSON serialization TTimeLogDynArray = array of TTimeLog; /// a type alias, which will be serialized as ISO-8601 with milliseconds // - i.e. 'YYYY-MM-DD hh:mm:ss.sss' or 'YYYYMMDD hhmmss.sss' format TDateTimeMS = type TDateTime; /// a dynamic array of TDateTimeMS values TDateTimeMSDynArray = array of TDateTimeMS; /// pointer to a dynamic array of TDateTimeMS values PDateTimeMSDynArray = ^TDateTimeMSDynArray; /// a 64-bit identifier, as used for our ORM primary key, i.e. TOrm.ID // - also maps the SQLite3 64-bit RowID definition TID = type Int64; /// a pointer to TOrm.ID, i.e. our ORM primary key PID = ^TID; /// used to store a dynamic array of ORM primary keys, i.e. TOrm.ID TIDDynArray = array of TID; /// pointer to a dynamic array of ORM primary keys, i.e. TOrm.ID PIDDynArray = ^TIDDynArray; /// timestamp stored as second-based Unix Time // - see Unix Time helper functions and types in mormot.core.datetime // - i.e. the number of seconds since 1970-01-01 00:00:00 UTC // - is stored as 64-bit value, so that it won't be affected by the // "Year 2038" overflow issue // - see TUnixMSTime for a millisecond resolution Unix Timestamp // - use UnixTimeToDateTime/DateTimeToUnixTime functions to convert it to/from // a regular TDateTime // - use UnixTimeUtc to return the current timestamp, using fast OS API call // - also one of the encodings supported by SQLite3 date/time functions TUnixTime = type Int64; /// pointer to a timestamp stored as second-based Unix Time PUnixTime = ^TUnixTime; /// dynamic array of timestamps stored as second-based Unix Time TUnixTimeDynArray = array of TUnixTime; /// timestamp stored as millisecond-based Unix Time // - see Unix Time helper functions and types in mormot.core.datetime // - i.e. the number of milliseconds since 1970-01-01 00:00:00 UTC // - see TUnixTime for a second resolution Unix Timestamp // - use UnixMSTimeToDateTime/DateTimeToUnixMSTime functions to convert it // to/from a regular TDateTime // - also one of the JavaScript date encodings TUnixMSTime = type Int64; /// pointer to a timestamp stored as millisecond-based Unix Time PUnixMSTime = ^TUnixMSTime; /// dynamic array of timestamps stored as millisecond-based Unix Time TUnixMSTimeDynArray = array of TUnixMSTime; const /// may be used to log as Trace or Warning event, depending on an Error: boolean LOG_TRACEWARNING: array[boolean] of TSynLogLevel = ( sllTrace, sllWarning); implementation {$ifdef ISDELPHI20062007} uses Windows; // circumvent unexpected warning about inlining (WTF!) {$endif ISDELPHI20062007} {$ifdef FPC} // globally disable some FPC paranoid warnings - rely on x86_64 as reference {$WARN 4056 off : Conversion between ordinals and pointers is not portable } {$endif FPC} { ************ Common Types Used for Compatibility Between Compilers and CPU } procedure VarClearAndSetType(var v: variant; vtype: integer); var p: PInteger; // more efficient generated asm with an explicit temp variable begin p := @v; {$if defined(OSBSDDARWIN) and defined(ARM3264)} if PVarData(p)^.VType and VTYPE_STATIC <> 0 then // just like in Variants.pas {$else} if p^ and VTYPE_STATIC <> 0 then {$ifend} VarClearProc(PVarData(p)^); p^ := vtype; end; {$ifdef HASINLINE} procedure VarClear(var v: variant); // defined here for proper inlining var p: PInteger; // more efficient generated asm with an explicit temp variable begin p := @v; {$if defined(OSBSDDARWIN) and defined(ARM3264)} if PVarData(p)^.VType and VTYPE_STATIC = 0 then // just like in Variants.pas {$else} if p^ and VTYPE_STATIC = 0 then {$ifend} p^ := 0 else VarClearProc(PVarData(p)^); end; {$endif HASINLINE} {$ifdef CPUARM} function ToByte(value: cardinal): cardinal; begin result := value and $ff; end; {$endif CPUARM} {$ifdef CPUX86} // directly use the x87 FPU stack procedure CurrencyToDouble(const c: currency; out d: double); begin d := c; end; procedure CurrencyToDouble(c: PCurrency; out d: double); begin d := c^; end; function CurrencyToDouble(c: PCurrency): double; begin result := c^; end; procedure DoubleToCurrency(const d: double; out c: currency); begin c := d; end; procedure DoubleToCurrency(const d: double; c: PCurrency); begin c^ := d; end; function DoubleToCurrency(const d: double): currency; begin result := d; end; {$else} // efficient inlined 64-bit integer version procedure CurrencyToDouble(const c: currency; out d: double); begin unaligned(d{%H-}) := PInt64(@c)^ / CURR_RES; end; procedure CurrencyToDouble(c: PCurrency; out d: double); begin unaligned(d{%H-}) := PInt64(c)^ / CURR_RES; end; function CurrencyToDouble(c: PCurrency): double; begin result := PInt64(c)^ / CURR_RES; end; procedure DoubleToCurrency(const d: double; out c: currency); begin PInt64(@c)^ := trunc(d * CURR_RES); end; procedure DoubleToCurrency(const d: double; c: PCurrency); begin PInt64(c)^ := trunc(d * CURR_RES); end; function DoubleToCurrency(const d: double): currency; begin result := trunc(d * CURR_RES); end; {$endif CPUX86} procedure CurrencyToInt64(c: PCurrency; var i: Int64); begin i := PInt64(c)^ div CURR_RES; end; procedure CurrencyToVariant(const c: currency; var v: variant); begin VarClearAndSetType(v, varCurrency); PVarData(@v).VCurrency := c; end; function SimpleRoundTo2Digits(Value: Currency): Currency; begin SimpleRoundTo2DigitsCurr64(PInt64(@Value)^); result := Value; end; procedure SimpleRoundTo2DigitsCurr64(var Value: Int64); var Spare: PtrInt; begin Spare := Value mod 100; if Spare <> 0 then if Spare > 50 then {%H-}inc(Value, 100 - Spare) else if Spare < -50 then {%H-}dec(Value, 100 + Spare) else dec(Value, Spare); end; function TwoDigits(const d: double): TShort31; var v: Int64; m, L: PtrInt; tmp: array[0..23] of AnsiChar; P: PAnsiChar; begin v := trunc(d * CURR_RES); m := v mod 100; if m <> 0 then if m > 50 then {%H-}inc(v, 100 - m) else if m < -50 then {%H-}dec(v, 100 + m) else dec(v, m); P := {%H-}StrInt64(@tmp[23], v); L := @tmp[22] - P; m := PWord(@tmp[L - 2])^; if m = ord('0') or ord('0') shl 8 then // '300' -> '3' dec(L, 3) else begin // '301' -> '3.01' PWord(@tmp[L - 1])^ := m; tmp[L - 2] := '.'; end; SetString(result, P, L); end; function TruncTo2Digits(Value: Currency): Currency; var V64: Int64 absolute Value; // to avoid any floating-point precision issues begin dec(V64, V64 mod 100); result := Value; end; procedure TruncTo2DigitsCurr64(var Value: Int64); begin dec(Value, Value mod 100); end; function TruncTo2Digits64(Value: Int64): Int64; begin result := Value - Value mod 100; end; procedure Int64ToCurrency(const i: Int64; out c: currency); begin PInt64(@c)^ := i * CURR_RES; end; procedure Int64ToCurrency(const i: Int64; c: PCurrency); begin PInt64(c)^ := i * CURR_RES; end; function IsEqualGuid({$ifdef FPC_HAS_CONSTREF}constref{$else}const{$endif} guid1, guid2: TGuid): boolean; begin result := (PHash128Rec(@guid1).L = PHash128Rec(@guid2).L) and (PHash128Rec(@guid1).H = PHash128Rec(@guid2).H); end; function IsEqualGuid(guid1, guid2: PGuid): boolean; begin result := (PHash128Rec(guid1).L = PHash128Rec(guid2).L) and (PHash128Rec(guid1).H = PHash128Rec(guid2).H); end; function IsEqualGuidArray(const guid: TGuid; const guids: array of TGuid): integer; begin result := Hash128Index(@guids[0], length(guids), @guid); end; function IsNullGuid({$ifdef FPC_HAS_CONSTREF}constref{$else}const{$endif} guid: TGuid): boolean; var a: TPtrIntArray absolute Guid; begin result := (a[0] = 0) and (a[1] = 0) {$ifdef CPU32} and (a[2] = 0) and (a[3] = 0) {$endif CPU32}; end; function AddGuid(var guids: TGuidDynArray; const guid: TGuid; NoDuplicates: boolean): integer; begin if NoDuplicates then begin result := Hash128Index(pointer(guids), length(guids), @guid); if result>=0 then exit; end; result := length(guids); SetLength(guids, result + 1); guids[result] := guid; end; procedure FillZero(var result: TGuid); var d: TInt64Array absolute result; begin d[0] := 0; d[1] := 0; end; function RandomGuid: TGuid; begin RandomGuid(result); end; procedure RandomGuid(out result: TGuid); begin // see https://datatracker.ietf.org/doc/html/rfc4122#section-4.4 RandomBytes(@result, SizeOf(TGuid)); result.D3 := (result.D3 and $0FFF) + $4000; // version bits 12-15 = 4 (random) result.D4[0] := byte(result.D4[0] and $3F) + $80; // reserved bits 6-7 = 1 end; function NextGrow(capacity: integer): integer; begin // algorithm similar to TFPList.Expand for the increasing ranges result := capacity; if result < 8 then inc(result, 4) // faster for smaller capacity (called often) else if result <= 128 then inc(result, 16) else if result < 8 shl 20 then inc(result, result shr 2) else if result < 128 shl 20 then inc(result, result shr 3) else inc(result, 16 shl 20); end; {$ifndef FPC_ASMX64} procedure FastAssignNew(var d; s: pointer); var sr: PStrRec; // local copy to use register begin sr := Pointer(d); Pointer(d) := s; if sr = nil then exit; dec(sr); if (sr^.refcnt >= 0) and StrCntDecFree(sr^.refcnt) then FreeMem(sr); end; procedure FastAssignNewNotVoid(var d; s: pointer); var sr: PStrRec; // local copy to use register begin sr := Pointer(d); Pointer(d) := s; dec(sr); if (sr^.refcnt >= 0) and StrCntDecFree(sr^.refcnt) then FreeMem(sr); end; {$endif FPC_ASMX64} function FastNewString(len, codepage: PtrInt): PAnsiChar; var P: PStrRec; begin result := nil; if len > 0 then begin {$ifdef FPC} P := GetMem(len + (_STRRECSIZE + 4)); result := PAnsiChar(P) + _STRRECSIZE; {$else} GetMem(result, len + (_STRRECSIZE + 4)); P := pointer(result); inc(PStrRec(result)); {$endif FPC} {$ifdef HASCODEPAGE} // also set elemSize := 1 {$ifdef FPC} P^.codePageElemSize := codepage + (1 shl 16); {$else} PCardinal(@P^.codePage)^ := codepage + (1 shl 16); {$endif FPC} {$endif HASCODEPAGE} P^.refCnt := 1; P^.length := len; PCardinal(PAnsiChar(P) + len + _STRRECSIZE)^ := 0; // ends with four #0 end; end; {$ifdef HASCODEPAGE} procedure EnsureRawUtf8(var s: RawByteString); begin if s <> '' then with PStrRec(PAnsiChar(pointer(s)) - _STRRECSIZE)^ do if CodePage <> CP_UTF8 then if refCnt <> 1 then FastSetString(RawUtf8(s), pointer(s), length) // make copy else CodePage := CP_UTF8; // just replace in-place end; procedure EnsureRawUtf8(var s: RawUtf8); begin EnsureRawUtf8(RawByteString(s)); end; procedure FakeCodePage(var s: RawByteString; cp: cardinal); var p: PAnsiChar; begin p := pointer(s); if p <> nil then PStrRec(p - _STRRECSIZE)^.CodePage := cp; end; function GetCodePage(const s: RawByteString): cardinal; begin result := PStrRec(PAnsiChar(pointer(s)) - _STRRECSIZE)^.CodePage; end; procedure FastAssignUtf8(var dest: RawUtf8; var src: RawByteString); begin FakeCodePage(RawByteString(src), CP_UTF8); FastAssignNew(dest, pointer(src)); pointer(src) := nil; // was assigned with no ref-counting involved end; {$else} // do nothing on Delphi 7-2007 procedure FakeCodePage(var s: RawByteString; cp: cardinal); begin end; procedure EnsureRawUtf8(var s: RawByteString); begin end; procedure EnsureRawUtf8(var s: RawUtf8); begin end; procedure FastAssignUtf8(var dest: RawUtf8; var src: RawByteString); begin FastAssignNew(dest, pointer(src)); pointer(src) := nil; // was assigned with no ref-counting involved end; {$endif HASCODEPAGE} procedure FakeLength(var s: RawUtf8; len: PtrInt); var p: PAnsiChar; // faster with a temp variable begin p := pointer(s); p[len] := #0; PStrLen(p - _STRLEN)^ := len; // in-place SetLength() end; procedure FakeLength(var s: RawUtf8; endChar: PUtf8Char); var p: PAnsiChar; begin p := pointer(s); endChar^ := #0; PStrLen(p - _STRLEN)^ := endChar - p; end; procedure FakeLength(var s: RawByteString; len: PtrInt); var p: PAnsiChar; begin p := pointer(s); p[len] := #0; PStrLen(p - _STRLEN)^ := len; // in-place SetLength() end; procedure FakeSetLength(var s: RawUtf8; len: PtrInt); begin if len <= 0 then FastAssignNew(s) else FakeLength(s, len); end; procedure FakeSetLength(var s: RawByteString; len: PtrInt); overload; begin if len <= 0 then FastAssignNew(s) else FakeLength(s, len); end; procedure FastSetStringCP(var s; p: pointer; len, codepage: PtrInt); var r: pointer; begin r := FastNewString(len, codepage); if (p <> nil) and (r <> nil) then MoveFast(p^, r^, len); if pointer(s) = nil then pointer(s) := r else FastAssignNewNotVoid(s, r); end; procedure FastSetString(var s: RawUtf8; p: pointer; len: PtrInt); var r: pointer; begin r := FastNewString(len, CP_UTF8); // FPC will do proper constant propagation if (p <> nil) and (r <> nil) then MoveFast(p^, r^, len); if pointer(s) = nil then pointer(s) := r else FastAssignNewNotVoid(s, r); end; procedure FastSetString(var s: RawUtf8; len: PtrInt); var r: pointer; begin r := FastNewString(len, CP_UTF8); if pointer(s) = nil then pointer(s) := r else FastAssignNewNotVoid(s, r); end; procedure FastSetRawByteString(var s: RawByteString; p: pointer; len: PtrInt); var r: pointer; begin r := FastNewString(len, CP_RAWBYTESTRING); // FPC does constant propagation if (p <> nil) and (r <> nil) then MoveFast(p^, r^, len); if pointer(s) = nil then pointer(s) := r else FastAssignNewNotVoid(s, r); end; procedure FastNewRawByteString(var s: RawByteString; len: PtrInt); var r: pointer; begin r := FastNewString(len, CP_RAWBYTESTRING); if pointer(s) = nil then pointer(s) := r else FastAssignNewNotVoid(s, r); end; procedure GetMemAligned(var holder: RawByteString; fillwith: pointer; len: PtrUInt; out aligned: pointer; alignment: PtrUInt); begin dec(alignment); // expected to be a power of two FastNewRawByteString(holder, len + alignment); aligned := pointer(holder); while PtrUInt(aligned) and alignment <> 0 do inc(PByte(aligned)); if fillwith <> nil then MoveFast(fillwith^, aligned^, len); end; // CompareMemSmall/MoveByOne defined now for proper inlining below // warning: Delphi has troubles inlining goto/label function CompareMemSmall(P1, P2: Pointer; Length: PtrInt): boolean; var c: AnsiChar; begin result := false; inc(PtrUInt(P1), PtrUInt(Length)); inc(PtrUInt(P2), PtrUInt(Length)); Length := -Length; if Length <> 0 then repeat c := PAnsiChar(P1)[Length]; if c <> PAnsiChar(P2)[Length] then exit; inc(Length); until Length = 0; result := true; end; procedure MoveByOne(Source, Dest: Pointer; Count: PtrUInt); var c: AnsiChar; // better code generation on FPC begin inc(PtrUInt(Source), Count); inc(PtrUInt(Dest), Count); PtrInt(Count) := -PtrInt(Count); repeat c := PAnsiChar(Source)[Count]; PAnsiChar(Dest)[Count] := c; inc(Count); until Count = 0; end; function UniqueRawUtf8(var u: RawUtf8): pointer; begin {$ifdef FPC} UniqueString(u); // @u[1] won't call UniqueString() under FPC :( {$endif FPC} result := @u[1]; end; function ShortStringToAnsi7String(const source: ShortString): RawByteString; begin FastSetString(RawUtf8(result), @source[1], ord(source[0])); end; procedure ShortStringToAnsi7String(const source: ShortString; var result: RawUtf8); begin FastSetString(result, @source[1], ord(source[0])); end; procedure Ansi7StringToShortString(const source: RawUtf8; var result: ShortString); begin SetString(result, PAnsiChar(pointer(source)), length(source)); end; procedure AppendShort(const src: ShortString; var dest: ShortString); var len: PtrInt; begin len := ord(src[0]); if (len = 0) or (len + ord(dest[0]) > 255) then exit; MoveFast(src[1], dest[ord(dest[0]) + 1], len); inc(dest[0], len); end; procedure AppendShortChar(chr: AnsiChar; var dest: ShortString); begin if dest[0] = #255 then exit; inc(dest[0]); dest[ord(dest[0])] := chr; end; const HexChars: array[0..15] of AnsiChar = '0123456789ABCDEF'; HexCharsLower: array[0..15] of AnsiChar = '0123456789abcdef'; procedure AppendShortByteHex(value: byte; var dest: ShortString); var len: PtrInt; begin len := ord(dest[0]); if len >= 254 then exit; dest[len + 1] := HexChars[value shr 4]; inc(len, 2); value := value and $0f; dest[len] := HexChars[value]; dest[0] := AnsiChar(len); end; procedure AppendShortTemp24(value, temp: PAnsiChar; dest: PAnsiChar); {$ifdef HASINLINE} inline; {$endif} var valuelen, destlen, newlen: PtrInt; begin valuelen := temp - value; destlen := ord(dest[0]); newlen := valuelen + destlen; if newlen > 255 then exit; dest[0] := AnsiChar(newlen); MoveFast(value^, dest[destlen + 1], valuelen); end; procedure AppendShortCardinal(value: cardinal; var dest: ShortString); var tmp: array[0..23] of AnsiChar; begin AppendShortTemp24(StrUInt32(@tmp[23], value), @tmp[23], @dest); end; procedure AppendShortInt64(value: Int64; var dest: ShortString); var tmp: array[0..23] of AnsiChar; begin AppendShortTemp24(StrInt64(@tmp[23], value), @tmp[23], @dest); end; procedure AppendShortBuffer(buf: PAnsiChar; len: integer; var dest: ShortString); begin if len < 0 then len := StrLen(buf); if (len = 0) or (len + ord(dest[0]) > 255) then exit; MoveFast(buf^, dest[ord(dest[0]) + 1], len); inc(dest[0], len); end; procedure AppendShortAnsi7String(const buf: RawByteString; var dest: ShortString); begin if buf <> '' then AppendShortBuffer(pointer(buf), PStrLen(PtrUInt(buf) - _STRLEN)^, dest); end; function ClassNameShort(C: TClass): PShortString; // new TObject.ClassName is UnicodeString (since Delphi 2009) -> inline code // with vmtClassName = UTF-8 encoded text stored in a ShortString = -44 begin result := PPointer(PtrInt(PtrUInt(C)) + vmtClassName)^; end; function ClassNameShort(Instance: TObject): PShortString; begin if Instance = nil then result := @NULCHAR // avoid GPF else result := PPointer(PPtrInt(Instance)^ + vmtClassName)^; end; procedure ClassToText(C: TClass; var result: RawUtf8); var P: PShortString; begin if C = nil then result := '' // avoid GPF else begin P := PPointer(PtrInt(PtrUInt(C)) + vmtClassName)^; FastSetString(result, @P^[1], ord(P^[0])); end; end; function ToText(C: TClass): RawUtf8; begin ClassToText(C, result); end; function GetClassParent(C: TClass): TClass; begin result := PPointer(PtrInt(PtrUInt(C)) + vmtParent)^; {$ifndef HASDIRECTTYPEINFO} // e.g. for Delphi and newer FPC if result <> nil then result := PPointer(result)^; {$endif HASDIRECTTYPEINFO} end; function PropNameEquals(P1, P2: PShortString): boolean; var P1P2Len: PtrInt; label zero; begin P1P2Len := ord(P1^[0]); if P1P2Len <> ord(P2^[0]) then goto zero; inc(PByte(P1)); inc(PByte(P2)); P1P2Len := PtrInt(@PByteArray(P1)[P1P2Len - SizeOf(cardinal)]); // 32-bit end if P1P2Len >= PtrInt(PtrUInt(P1)) then repeat // case-insensitive compare 4 bytes per loop if (PCardinal(P1)^ xor PCardinal(P2)^) and $dfdfdfdf <> 0 then goto zero; inc(PCardinal(P1)); inc(PCardinal(P2)); until P1P2Len < PtrInt(PtrUInt(P1)); inc(PCardinal(P1P2Len)); dec(PtrUInt(P2), PtrUInt(P1)); if PtrInt(PtrUInt(P1)) < P1P2Len then repeat if (PByte(P1)^ xor PByteArray(P2)[PtrUInt(P1)]) and $df <> 0 then goto zero; inc(PByte(P1)); until PtrInt(PtrUInt(P1)) >= P1P2Len; result := true; exit; zero: result := false; end; function PropNameEquals(const P1, P2: RawUtf8): boolean; var P1P2Len, _1, _2: PtrInt; label zero; begin P1P2Len := length(P1); if P1P2Len <> length(P2) then goto zero; _1 := PtrUInt(P1); _2 := PtrUInt(P2); P1P2Len := PtrInt(@PByteArray(_1)[P1P2Len - SizeOf(cardinal)]); // 32-bit end if P1P2Len >= _1 then repeat // case-insensitive compare 4 bytes per loop if (PCardinal(_1)^ xor PCardinal(_2)^) and $dfdfdfdf <> 0 then goto zero; inc(PCardinal(_1)); inc(PCardinal(_2)); until P1P2Len < _1; inc(PCardinal(P1P2Len)); dec(_2, _1); if _1 < P1P2Len then repeat if (PByte(_1)^ xor PByteArray(_2)[PtrUInt(_1)]) and $df <> 0 then goto zero; inc(PByte(_1)); until _1 >= P1P2Len; result := true; exit; zero: result := false; end; {$ifdef HASINLINE} // defined here for proper inlining function CompareMemFixed(P1, P2: Pointer; Length: PtrInt): boolean; label zero; begin // cut-down version of our pure pascal CompareMem() function {$ifndef CPUX86} result := false; {$endif CPUX86} Length := PtrInt(@PAnsiChar(P1)[Length - SizeOf(PtrInt)]); if Length >= PtrInt(PtrUInt(P1)) then repeat // compare one PtrInt per loop if PPtrInt(P1)^ <> PPtrInt(P2)^ then goto zero; inc(PPtrInt(P1)); inc(PPtrInt(P2)); until Length < PtrInt(PtrUInt(P1)); inc(Length, SizeOf(PtrInt)); dec(PtrUInt(P2), PtrUInt(P1)); if PtrInt(PtrUInt(P1)) < Length then repeat if PByte(P1)^ <> PByteArray(P2)[PtrUInt(P1)] then goto zero; inc(PByte(P1)); until PtrInt(PtrUInt(P1)) >= Length; result := true; exit; zero: {$ifdef CPUX86} result := false; {$endif CPUX86} end; {$endif HASINLINE} function FindNonVoidRawUtf8(n: PPointerArray; name: pointer; len: TStrLen; count: PtrInt): PtrInt; var p: PUtf8Char; begin // FPC does proper inlining in this loop result := 0; repeat p := n[result]; // all VName[]<>'' so p=n^<>nil if (PStrLen(p - _STRLEN)^ = len) and (p^ = PAnsiChar(name)^) and ((len = 1) or CompareMemFixed(p + 1, PAnsiChar(name) + 1, len - 1)) then exit; inc(result); dec(count); until count = 0; result := -1; end; function FindNonVoidRawUtf8I(n: PPointerArray; name: pointer; len: TStrLen; count: PtrInt): PtrInt; var p1, p2, l: PUtf8Char; label no; begin result := 0; p2 := name; repeat // inlined IdemPropNameUSameLenNotNull(p, name, len) p1 := n[result]; // all VName[]<>'' so p1<>nil if (PStrLen(p1 - _STRLEN)^ = len) and ((ord(p1^) xor ord(p2^)) and $df = 0) then begin if len = 1 then exit; inc(p1); inc(p2); l := @p1[len - (SizeOf(cardinal) + 1)]; dec(p2, PtrUInt(p1)); while PtrUInt(l) >= PtrUInt(p1) do // compare 4 Bytes per loop if (PCardinal(p1)^ xor PCardinal(@p2[PtrUInt(p1)])^) and $dfdfdfdf <> 0 then goto no else inc(PCardinal(p1)); inc(PCardinal(l)); while PtrUInt(p1) < PtrUInt(l) do // remaining bytes if (ord(p1^) xor ord(p2[PtrUInt(p1)])) and $df <> 0 then goto no else inc(PByte(p1)); exit; // match found no: p2 := name; end; inc(result); dec(count); until count = 0; result := -1; end; function FindPropName(Values: PRawUtf8Array; const Value: RawUtf8; ValuesCount: PtrInt): PtrInt; begin if (Values <> nil) and (ValuesCount > 0) and (Value <> '') then result := FindNonVoidRawUtf8I(pointer(Values), pointer(Value), PStrLen(PAnsiChar(pointer(Value)) - _STRLEN)^, ValuesCount) else result := -1; end; function FindPropName(const Names: array of RawUtf8; const Name: RawUtf8): integer; begin result := high(Names); if result >= 0 then result := FindPropName(@Names[0], Name, result + 1); end; function DateTimeToIsoString(dt: TDateTime): string; begin // avoid to link mormot.core.datetime DateTimeToString(result, 'yyyy-mm-dd hh:nn:ss', dt); end; procedure ToHumanHex(var result: RawUtf8; bin: PByteArray; len: PtrInt); var P: PAnsiChar; i, c: PtrInt; tab: PAnsichar; begin if len <= 0 then begin result := ''; exit; end; FastSetString(result, (len * 3) - 1); dec(len); tab := @HexCharsLower; P := pointer(result); i := 0; repeat c := bin[i]; P[0] := tab[c shr 4]; c := c and 15; P[1] := tab[c]; if i = len then break; P[2] := ':'; // to please (most) human limited hexadecimal capabilities inc(P, 3); inc(i); until false; end; procedure ToHumanHexReverse(var result: RawUtf8; bin: PByteArray; len: PtrInt); var P: PAnsiChar; i, c: PtrInt; tab: PAnsichar; begin if len <= 0 then begin result := ''; exit; end; FastSetString(result, (len * 3) - 1); tab := @HexCharsLower; P := pointer(result); i := len; repeat dec(i); c := bin[i]; P[0] := tab[c shr 4]; c := c and 15; P[1] := tab[c]; if i = 0 then break; P[2] := ':'; inc(P, 3); until false; end; { ************ Numbers (floats and integers) Low-level Definitions } function GetInteger(P: PUtf8Char): PtrInt; var c: byte; minus: boolean; begin result := 0; if P = nil then exit; c := byte(P^); repeat if c = 0 then exit; if c > ord(' ') then break; inc(P); c := byte(P^); until false; if c = ord('-') then begin minus := true; repeat inc(P); c := byte(P^); until c <> ord(' '); end else begin minus := false; if c = ord('+') then repeat inc(P); c := byte(P^); until c <> ord(' '); end; dec(c, 48); if c > 9 then exit; result := c; repeat inc(P); c := byte(P^); dec(c, 48); if c > 9 then break; result := result * 10 + PtrInt(c); until false; if minus then result := -result; end; function GetInteger(P, PEnd: PUtf8Char): PtrInt; var c: byte; minus: boolean; begin result := 0; if (P = nil) or (P >= PEnd) then exit; c := byte(P^); repeat if c = 0 then exit; if c > ord(' ') then break; inc(P); if P = PEnd then exit; c := byte(P^); until false; if c = ord('-') then begin minus := true; repeat inc(P); if P = PEnd then exit; c := byte(P^); until c <> ord(' '); end else begin minus := false; if c = ord('+') then repeat inc(P); if P = PEnd then exit; c := byte(P^); until c <> ord(' '); end; dec(c, 48); if c > 9 then exit; result := c; repeat inc(P); if P = PEnd then break; c := byte(P^); dec(c, 48); if c > 9 then break; result := result * 10 + PtrInt(c); until false; if minus then result := -result; end; function GetInteger(P: PUtf8Char; var err: integer): PtrInt; var c: byte; minus: boolean; begin result := 0; err := 1; // don't return the exact index, just 1 as error flag if P = nil then exit; c := byte(P^); repeat if c = 0 then exit; if c > ord(' ') then break; inc(P); c := byte(P^); until false; if c = ord('-') then begin minus := true; repeat inc(P); c := byte(P^); until c <> ord(' '); end else begin minus := false; if c = ord('+') then repeat inc(P); c := byte(P^); until c <> ord(' '); end; dec(c, 48); if c > 9 then exit; result := c; repeat inc(P); c := byte(P^); dec(c, 48); if c <= 9 then result := result * 10 + PtrInt(c) else if c <> 256 - 48 then exit else break; until false; err := 0; // success if minus then result := -result; end; function GetIntegerDef(P: PUtf8Char; Default: PtrInt): PtrInt; var err: integer; begin result := GetInteger(P, err); if err <> 0 then result := Default; end; function GetBoolean(P: PUtf8Char): boolean; begin result := (P <> nil) and (PInteger(P)^ <> FALSE_LOW) and ((PInteger(P)^ = TRUE_LOW) or ((PInteger(P)^ and $ffff) <> ord('0'))); end; function GetBoolean(const value: RawUtf8): boolean; begin result := GetBoolean(pointer(value)); end; function GetTrue(P: PUtf8Char): integer; begin result := PInteger(P)^ and $dfdfdfdf; if (result = ord('T') + ord('R') shl 8 + ord('U') shl 16 + ord('E') shl 24) or (result = ord('Y') + ord('E') shl 8 + ord('S') shl 16) then result := 1 else result := 0; end; function GetInt64Bool(P: PUtf8Char; out V: Int64): boolean; var err, c: integer; begin result := P <> nil; if not result then exit; V := GetInt64(P, err); if err = 0 then exit; c := PInteger(P)^ and $dfdfdfdf; if (c = ord('F') + ord('A') shl 8 + ord('L') shl 16 + ord('S') shl 24) or (c and $ffffff = ord('N') + ord('O') shl 8) then V := 0 else if (c = ord('T') + ord('R') shl 8 + ord('U') shl 16 + ord('E') shl 24) or (c = ord('Y') + ord('E') shl 8 + ord('S') shl 16) then V := 1 else result := false; end; function GetCardinalDef(P: PUtf8Char; Default: PtrUInt): PtrUInt; var c: byte; begin result := Default; if P = nil then exit; c := byte(P^); repeat if c = 0 then exit; if c > ord(' ') then break; inc(P); c := byte(P^); until false; dec(c, 48); if c > 9 then exit; result := c; repeat inc(P); c := byte(P^) - 48; if c > 9 then break; result := result * 10 + PtrUInt(c); until false; end; function GetCardinal(P: PUtf8Char): PtrUInt; var c: byte; begin result := 0; if P = nil then exit; c := byte(P^); repeat if c = 0 then exit; if c > ord(' ') then break; inc(P); c := byte(P^); until false; dec(c, 48); if c > 9 then exit; result := c; repeat inc(P); c := byte(P^); dec(c, 48); if c > 9 then break; result := result * 10 + PtrUInt(c); until false; end; function GetCardinal(P, PEnd: PUtf8Char): PtrUInt; var c: byte; begin result := 0; if (P = nil) or (P >= PEnd) then exit; c := byte(P^); repeat if c = 0 then exit; if c > ord(' ') then break; inc(P); if P = PEnd then exit; c := byte(P^); until false; dec(c, 48); if c > 9 then exit; result := c; repeat inc(P); if P = PEnd then break; c := byte(P^); dec(c, 48); if c > 9 then break; result := result * 10 + PtrUInt(c); until false; end; function GetCardinalW(P: PWideChar): PtrUInt; var c: PtrUInt; begin result := 0; if P = nil then exit; c := ord(P^); repeat if c = 0 then exit; if c > ord(' ') then break; inc(P); c := ord(P^); until false; dec(c, 48); if c > 9 then exit; result := c; repeat inc(P); c := ord(P^); dec(c, 48); if c > 9 then break; result := result * 10 + c; until false; end; function GetInt64Def(P: PUtf8Char; const Default: Int64): Int64; var err: integer; begin result := GetInt64(P, err); if err > 0 then result := Default; end; {$ifdef CPU64} // PtrInt/PtrUInt are already Int64/QWord procedure SetInt64(P: PUtf8Char; var result: Int64); begin result := GetInteger(P); end; procedure SetQWord(P: PUtf8Char; var result: QWord); begin result := GetCardinal(P); end; procedure SetQWord(P, PEnd: PUtf8Char; var result: QWord); begin result := GetCardinal(P, PEnd); end; function GetInt64(P: PUtf8Char): Int64; begin result := GetInteger(P); end; function GetInt64(P: PUtf8Char; var err: integer): Int64; begin result := GetInteger(P, err); end; function StrUInt64(P: PAnsiChar; const val: QWord): PAnsiChar; begin result := StrUInt32(P, val); // StrUInt32 converts PtrUInt=QWord on 64-bit CPU end; function StrInt64(P: PAnsiChar; const val: Int64): PAnsiChar; begin result := StrInt32(P, val); // StrInt32 converts PtrInt=Int64 on 64-bit CPU end; function GetQWord(P: PUtf8Char; var err: integer): QWord; var c: PtrUInt; begin err := 1; // error result := 0; if P = nil then exit; while (P^ <= ' ') and (P^ <> #0) do inc(P); c := byte(P^) - 48; if c > 9 then exit; result := c; inc(P); repeat c := byte(P^); if c = 0 then break; dec(c, 48); if c > 9 then exit; result := result * 10 + c; inc(P); until false; err := 0; // success end; {$else} // 32-bit dedicated code - use integer/cardinal as much as possible procedure SetInt64(P: PUtf8Char; var result: Int64); var c: cardinal; minus: boolean; begin result := 0; if P = nil then exit; while (P^ <= ' ') and (P^ <> #0) do inc(P); if P^ = '-' then begin minus := true; repeat inc(P) until P^ <> ' '; end else begin minus := false; if P^ = '+' then repeat inc(P) until P^ <> ' '; end; c := byte(P^) - 48; if c > 9 then exit; PCardinal(@result)^ := c; inc(P); repeat // fast 32-bit loop c := byte(P^) - 48; if c > 9 then break else PCardinal(@result)^ := PCardinal(@result)^ * 10 + c; inc(P); if PCardinal(@result)^ >= high(cardinal) div 10 then begin repeat // 64-bit loop c := byte(P^) - 48; if c > 9 then break; result := result shl 3 + result + result; // fast result := result*10 inc(result, c); inc(P); until false; break; end; until false; if minus then result := -result; end; procedure SetQWord(P: PUtf8Char; var result: QWord); var c: cardinal; begin result := 0; if P = nil then exit; while (P^ <= ' ') and (P^ <> #0) do inc(P); if P^ = '+' then repeat inc(P) until P^ <> ' '; c := byte(P^) - 48; if c > 9 then exit; PCardinal(@result)^ := c; inc(P); repeat // fast 32-bit loop c := byte(P^) - 48; if c > 9 then break else PCardinal(@result)^ := PCardinal(@result)^ * 10 + c; inc(P); if PCardinal(@result)^ >= high(cardinal) div 10 then begin repeat // 64-bit loop c := byte(P^) - 48; if c > 9 then break; result := result shl 3 + result + result; // fast result := result*10 inc(result, c); inc(P); until false; break; end; until false; end; procedure SetQWord(P, PEnd: PUtf8Char; var result: QWord); var c: cardinal; begin result := 0; if (P = nil) or (P >= PEnd) then exit; while P^ <= ' ' do if P = PEnd then exit else inc(P); if P^ = '+' then repeat inc(P); if P = PEnd then exit; until P^ <> ' '; c := byte(P^) - 48; if c > 9 then exit; PCardinal(@result)^ := c; inc(P); repeat // fast 32-bit loop if P = PEnd then break; c := byte(P^) - 48; if c > 9 then break else PCardinal(@result)^ := PCardinal(@result)^ * 10 + c; inc(P); if PCardinal(@result)^ >= high(cardinal) div 10 then begin repeat // 64-bit loop if P = PEnd then exit; c := byte(P^) - 48; if c > 9 then break; result := result shl 3 + result + result; // fast result := result*10 inc(result, c); inc(P); until false; break; end; until false; end; function GetInt64(P: PUtf8Char): Int64; begin SetInt64(P, result); end; function GetInt64(P: PUtf8Char; var err: integer): Int64; var c: cardinal; minus: boolean; begin err := 0; result := 0; if P = nil then exit; while (P^ <= ' ') and (P^ <> #0) do inc(P); if P^ = '-' then begin minus := true; repeat inc(P) until P^ <> ' '; end else begin minus := false; if P^ = '+' then repeat inc(P) until P^ <> ' '; end; inc(err); c := byte(P^) - 48; if c > 9 then exit; PCardinal(@result)^ := c; inc(P); repeat // fast 32-bit loop c := byte(P^); if c <> 0 then begin dec(c, 48); inc(err); if c > 9 then exit; PCardinal(@result)^ := PCardinal(@result)^ * 10 + c; inc(P); if PCardinal(@result)^ >= high(cardinal) div 10 then begin repeat // 64-bit loop c := byte(P^); if c = 0 then begin err := 0; // conversion success without error break; end; dec(c, 48); inc(err); if c > 9 then exit else {$ifdef CPU32DELPHI} result := result shl 3 + result + result; {$else} result := result * 10; {$endif CPU32DELPHI} inc(result, c); if result < 0 then exit; // overflow (>$7FFFFFFFFFFFFFFF) inc(P); until false; break; end; end else begin err := 0; // reached P^=#0 -> conversion success without error break; end; until false; if minus then result := -result; end; function GetQWord(P: PUtf8Char; var err: integer): QWord; var c: PtrUInt; begin err := 1; // error result := 0; if P = nil then exit; while (P^ <= ' ') and (P^ <> #0) do inc(P); c := byte(P^) - 48; if c > 9 then exit; PByte(@result)^ := c; inc(P); repeat // fast 32-bit loop c := byte(P^); if c <> 0 then begin dec(c, 48); inc(err); if c > 9 then exit; PCardinal(@result)^ := PCardinal(@result)^ * 10 + c; inc(P); if PCardinal(@result)^ >= high(cardinal) div 10 then begin repeat // 64-bit loop c := byte(P^); if c = 0 then begin err := 0; // conversion success without error break; end; dec(c, 48); inc(err); if c > 9 then exit else {$ifdef CPU32DELPHI} result := result shl 3 + result + result; {$else} result := result * 10; {$endif CPU32DELPHI} inc(result, c); inc(P); until false; break; end; end else begin err := 0; // reached P^=#0 -> conversion success without error break; end; until false; end; function StrUInt64(P: PAnsiChar; const val: QWord): PAnsiChar; var c, c100: QWord; {$ifdef CPUX86NOTPIC} tab: TWordArray absolute TwoDigitLookupW; {$else} tab: PWordArray; {$endif CPUX86NOTPIC} begin if PCardinalArray(@val)^[1] = 0 then P := StrUInt32(P, PCardinal(@val)^) else begin {$ifndef CPUX86NOTPIC} tab := @TwoDigitLookupW; {$endif CPUX86NOTPIC} c := val; repeat {$ifdef CPUX86} asm // by-passing the RTL is a good idea here push ebx mov edx, dword ptr [c + 4] mov eax, dword ptr [c] mov ebx, 100 mov ecx, eax mov eax, edx xor edx, edx div ebx mov dword ptr [c100 + 4], eax xchg eax, ecx div ebx mov dword ptr [c100], eax imul ebx, ecx mov ecx, 100 mul ecx add edx, ebx pop ebx sub dword ptr [c + 4], edx sbb dword ptr [c], eax end; {$else} c100 := c div 100; // one div by two digits dec(c, c100 * 100); // fast c := c mod 100 {$endif CPUX86} dec(P, 2); PWord(P)^ := tab[c]; c := c100; if (PCardinalArray(@c)^[1] = 0) then begin if PCardinal(@c)^ <> 0 then P := StrUInt32(P, PCardinal(@c)^); break; end; until false; end; result := P; end; function StrInt64(P: PAnsiChar; const val: Int64): PAnsiChar; begin if val < 0 then begin P := StrUInt64(P, -val) - 1; P^ := '-'; end else P := StrUInt64(P, val); result := P; end; {$endif CPU64} function GetExtended(P: PUtf8Char): TSynExtended; var err: integer; begin result := GetExtended(P, err); if err <> 0 then result := 0; end; function HugePower10Pos(exponent: PtrInt; pow10: PPow10): TSynExtended; begin result := pow10[(exponent and not 31) shr 5 + 34] * pow10[exponent and 31]; end; function HugePower10Neg(exponent: PtrInt; pow10: PPow10): TSynExtended; begin exponent := -exponent; result := pow10[(exponent and not 31) shr 5 + 45] / pow10[exponent and 31]; end; {$ifndef CPU32DELPHI} function GetExtended(P: PUtf8Char; out err: integer): TSynExtended; var remdigit: integer; frac, exp: PtrInt; c: AnsiChar; flags: set of (fNeg, fNegExp, fValid); v64: Int64; // allows 64-bit resolution for the digits (match 80-bit extended) label e; begin byte(flags) := 0; v64 := 0; frac := 0; if P = nil then goto e; // will return 0 but err=1 c := P^; if c = ' ' then repeat inc(P); c := P^; until c <> ' '; // trailing spaces if c = '+' then begin inc(P); c := P^; end else if c = '-' then begin inc(P); c := P^; include(flags, fNeg); end; remdigit := 19; // max Int64 resolution repeat inc(P); if (c >= '0') and (c <= '9') then begin dec(remdigit); if remdigit >= 0 then // over-required digits are just ignored begin dec(c, ord('0')); {$ifdef CPU64} v64 := v64 * 10; {$else} v64 := v64 shl 3 + v64 + v64; {$endif CPU64} inc(v64, byte(c)); c := P^; include(flags, fValid); if frac <> 0 then dec(frac); // digits after '.' continue; end; if frac >= 0 then inc(frac); // handle #############00000 c := P^; continue; end; if c <> '.' then break; if frac > 0 then goto e; // will return partial value but err=1 dec(frac); c := P^; until false; if frac < 0 then inc(frac); // adjust digits after '.' if (c = 'E') or (c = 'e') then begin exp := 0; exclude(flags, fValid); c := P^; if c = '+' then inc(P) else if c = '-' then begin inc(P); include(flags, fNegExp); end; repeat c := P^; inc(P); if (c < '0') or (c > '9') then break; dec(c, ord('0')); exp := (exp * 10) + byte(c); include(flags, fValid); until false; if fNegExp in flags then dec(frac, exp) else inc(frac, exp); if (frac <= -324) or (frac >= 308) then begin frac := 0; goto e; // limit to 5.0 x 10^-324 .. 1.7 x 10^308 double range end; end; if (fValid in flags) and (c = #0) then err := 0 else e: err := 1; // return the (partial) value even if not ended with #0 exp := PtrUInt(@POW10); if frac >= -31 then if frac <= 31 then result := PPow10(exp)[frac] // -31 .. + 31 else result := HugePower10Pos(frac, PPow10(exp)) // +32 .. else result := HugePower10Neg(frac, PPow10(exp)); // .. -32 if fNeg in flags then result := result * PPow10(exp)[33]; // * -1 result := result * v64; end; {$endif CPU32DELPHI} function Utf8ToInteger(const value: RawUtf8; Default: PtrInt): PtrInt; var err: integer; begin result := GetInteger(pointer(value), err); if err <> 0 then result := Default; end; function Utf8ToInteger(const value: RawUtf8; min, max, default: PtrInt): PtrInt; var err: integer; begin result := GetInteger(pointer(value), err); if (err <> 0) or (result < min) or (result > max) then result := default; end; function ToInteger(const text: RawUtf8; out value: integer): boolean; var v, err: integer; begin v := GetInteger(pointer(text), err); result := err = 0; if result then value := v; end; function ToCardinal(const text: RawUtf8; out value: cardinal; minimal: cardinal): boolean; var v: cardinal; begin v := GetCardinalDef(pointer(text), cardinal(-1)); result := (v <> cardinal(-1)) and (v >= minimal); if result then value := v; end; function ToInt64(const text: RawUtf8; out value: Int64): boolean; var err: integer; v: Int64; begin v := GetInt64(pointer(text), err); result := err = 0; if result then value := v; end; function ToDouble(const text: RawUtf8; out value: double): boolean; var err: integer; v: double; begin v := GetExtended(pointer(text), err); result := err = 0; if result then value := v; end; function Utf8ToInt64(const text: RawUtf8; const default: Int64): Int64; var err: integer; begin result := GetInt64(pointer(text), err); if err <> 0 then result := default; end; { ************ integer arrays manipulation } function IsZero(const Values: TIntegerDynArray): boolean; var i: PtrInt; begin result := false; for i := 0 to length(Values) - 1 do if Values[i] <> 0 then exit; result := true; end; function IsZero(const Values: TInt64DynArray): boolean; var i: PtrInt; begin result := false; for i := 0 to length(Values) - 1 do if Values[i] <> 0 then exit; result := true; end; procedure FillZero(var Values: TIntegerDynArray); begin FillCharFast(Values[0], length(Values) * SizeOf(integer), 0); end; procedure FillZero(var Values: TInt64DynArray); begin FillCharFast(Values[0], length(Values) * SizeOf(Int64), 0); end; function CompareInteger(const A, B: integer): integer; begin result := ord(A > B) - ord(A < B); end; function CompareCardinal(const A, B: cardinal): integer; begin result := ord(A > B) - ord(A < B); end; function ComparePtrInt(const A, B: PtrInt): integer; begin result := ord(A > B) - ord(A < B); end; function ComparePointer(const A, B: pointer): integer; begin result := ord(PtrUInt(A) > PtrUInt(B)) - ord(PtrUInt(A) < PtrUInt(B)); end; {$ifdef FPC_OR_UNICODE} // recent compilers are able to generate correct code function CompareInt64(const A, B: Int64): integer; begin result := ord(A > B) - ord(A < B); end; function CompareQword(const A, B: QWord): integer; begin result := ord(A > B) - ord(A < B); end; {$else} function CompareInt64(const A, B: Int64): integer; begin // Delphi x86 compiler is not efficient at compiling Int64 comparisons result := SortDynArrayInt64(A, B); end; function CompareQword(const A, B: QWord): integer; begin // Delphi x86 compiler is not efficient, and oldest even incorrect result := SortDynArrayQWord(A, B); end; {$endif FPC_OR_UNICODE} function Int64ScanExists(P: PInt64Array; Count: PtrInt; const Value: Int64): boolean; begin if P <> nil then begin result := true; Count := PtrUInt(@P[Count - 4]); repeat if PtrUInt(P) > PtrUInt(Count) then break; if (P^[0] = Value) or (P^[1] = Value) or (P^[2] = Value) or (P^[3] = Value) then exit; P := @P[4]; until false; inc(Count, 4 * SizeOf(Value)); repeat if PtrUInt(P) >= PtrUInt(Count) then break; if P^[0] = Value then exit; P := @P[1]; until false; end; result := false; end; function Int64Scan(P: PInt64Array; Count: PtrInt; const Value: Int64): PInt64; begin result := nil; if P = nil then exit; Count := PtrUInt(@P[Count - 4]); repeat if PtrUInt(P) > PtrUInt(Count) then break; if P^[0] <> Value then if P^[1] <> Value then if P^[2] <> Value then if P^[3] <> Value then begin P := @P[4]; continue; end else result := @P[3] else result := @P[2] else result := @P[1] else result := pointer(P); exit; until false; inc(Count, 4 * SizeOf(Value)); result := pointer(P); repeat if PtrUInt(result) >= PtrUInt(Count) then break; if result^ = Value then exit; inc(result); until false; result := nil; end; function Int64ScanIndex(P: PInt64Array; Count: PtrInt; const Value: Int64): PtrInt; begin result := PtrUInt(Int64Scan(P, Count, Value)); if result = 0 then dec(result) else begin dec(result, PtrUInt(P)); result := result shr 3; end; end; function QWordScanIndex(P: PQWordArray; Count: PtrInt; const Value: QWord): PtrInt; begin result := Int64ScanIndex(pointer(P), Count, Value); // this is the very same code end; {$ifdef CPU64} // PtrInt = Int64 and PtrUInt = QWord function PtrUIntScan(P: PPtrUIntArray; Count: PtrInt; Value: PtrUInt): pointer; begin result := Int64Scan(pointer(P), Count, Value); end; function PtrUIntScanExists(P: PPtrUIntArray; Count: PtrInt; Value: PtrUInt): boolean; begin result := Int64ScanExists(pointer(P), Count, Value); end; function PtrUIntScanIndex(P: PPtrUIntArray; Count: PtrInt; Value: PtrUInt): PtrInt; begin result := Int64ScanIndex(pointer(P), Count, Value); end; procedure QuickSortPtrInt(P: PPtrIntArray; L, R: PtrInt); begin QuickSortInt64(PInt64Array(P), L, R); end; procedure QuickSortPointer(P: PPointerArray; L, R: PtrInt); begin QuickSortInt64(PInt64Array(P), L, R); end; function FastFindPtrIntSorted(P: PPtrIntArray; R: PtrInt; Value: PtrInt): PtrInt; begin result := FastFindInt64Sorted(PInt64Array(P), R, Value); end; function FastFindPointerSorted(P: PPointerArray; R: PtrInt; Value: pointer): PtrInt; begin result := FastFindInt64Sorted(PInt64Array(P), R, Int64(Value)); end; {$else} // PtrInt = integer and PtrUInt = cardinal function PtrUIntScan(P: PPtrUIntArray; Count: PtrInt; Value: PtrUInt): pointer; begin result := IntegerScan(pointer(P), Count, Value); end; function PtrUIntScanExists(P: PPtrUIntArray; Count: PtrInt; Value: PtrUInt): boolean; begin result := IntegerScanExists(pointer(P), Count, Value); end; function PtrUIntScanIndex(P: PPtrUIntArray; Count: PtrInt; Value: PtrUInt): PtrInt; begin result := IntegerScanIndex(pointer(P), Count, Value); end; procedure QuickSortPtrInt(P: PPtrIntArray; L, R: PtrInt); begin QuickSortInteger(PIntegerArray(P), L, R); end; procedure QuickSortPointer(P: PPointerArray; L, R: PtrInt); begin QuickSortInteger(PIntegerArray(P), L, R); end; function FastFindPtrIntSorted(P: PPtrIntArray; R: PtrInt; Value: PtrInt): PtrInt; begin result := FastFindIntegerSorted(PIntegerArray(P), R, Value); end; function FastFindPointerSorted(P: PPointerArray; R: PtrInt; Value: pointer): PtrInt; begin result := FastFindIntegerSorted(PIntegerArray(P), R, integer(Value)); end; {$endif CPU64} procedure DynArrayFakeLength(arr: pointer; len: TDALen); begin PDALen(PAnsiChar(arr) - _DALEN)^ := len - _DAOFF; end; {$ifdef FPC} // some FPC-specific low-level code due to diverse compiler or RTL function TDynArrayRec.GetLength: TDALen; begin result := high + 1; end; procedure TDynArrayRec.SetLength(len: TDALen); begin high := len - 1; end; procedure Div100(Y: cardinal; var res: TDiv100Rec); // Delphi=asm, FPC=inlined var Y100: cardinal; begin Y100 := Y div 100; // FPC will use fast reciprocal res.D := Y100; res.M := Y {%H-}- Y100 * 100; // avoid div twice end; {$endif FPC} function AddInteger(var Values: TIntegerDynArray; Value: integer; NoDuplicates: boolean): boolean; var n: PtrInt; begin n := Length(Values); if NoDuplicates and IntegerScanExists(pointer(Values), n, Value) then begin result := false; exit; end; SetLength(Values, n + 1); Values[n] := Value; result := true end; procedure AddInteger(var Values: TIntegerDynArray; var ValuesCount: integer; Value: integer); begin if ValuesCount = Length(Values) then SetLength(Values, NextGrow(ValuesCount)); Values[ValuesCount] := Value; inc(ValuesCount); end; function AddInteger(var Values: TIntegerDynArray; var ValuesCount: integer; Value: integer; NoDuplicates: boolean): boolean; begin if NoDuplicates and IntegerScanExists(pointer(Values), ValuesCount, Value) then begin result := false; exit; end; if ValuesCount = Length(Values) then SetLength(Values, NextGrow(ValuesCount)); Values[ValuesCount] := Value; inc(ValuesCount); result := true; end; function AddInteger(var Values: TIntegerDynArray; const Another: TIntegerDynArray): PtrInt; var v, a: PtrInt; begin v := Length(Values); a := Length(Another); if a > 0 then begin SetLength(Values, v + a); MoveFast(Another[0], Values[v], a * SizeOf(integer)); end; result := v + a; end; function AddWord(var Values: TWordDynArray; var ValuesCount: integer; Value: Word): PtrInt; begin result := ValuesCount; if result = Length(Values) then SetLength(Values, NextGrow(result)); Values[result] := Value; inc(ValuesCount); end; function AddInt64(var Values: TInt64DynArray; var ValuesCount: integer; Value: Int64): PtrInt; begin result := ValuesCount; if result = Length(Values) then SetLength(Values, NextGrow(result)); Values[result] := Value; inc(ValuesCount); end; function AddInt64(var Values: TInt64DynArray; Value: Int64): PtrInt; begin result := Length(Values); SetLength(Values, result + 1); Values[result] := Value; end; function AddInt64(var Values: TInt64DynArray; const Another: TInt64DynArray): PtrInt; var v, a: PtrInt; begin v := Length(Values); a := Length(Another); if a > 0 then begin SetLength(Values, v + a); MoveFast(Another[0], Values[v], a * SizeOf(Int64)); end; result := v + a; end; function AddPtrUInt(var Values: TPtrUIntDynArray; var ValuesCount: integer; Value: PtrUInt): PtrInt; begin result := ValuesCount; if result = Length(Values) then SetLength(Values, NextGrow(result)); Values[result] := Value; inc(ValuesCount); end; procedure AddInt64Sorted(var Values: TInt64DynArray; Value: Int64); var last: integer; begin last := high(Values); if FastFindInt64Sorted(pointer(Values), last, Value) >= 0 then exit; // found inc(last); SetLength(Values, last + 1); Values[last] := Value; QuickSortInt64(pointer(Values), 0, last); end; function AddInt64Once(var Values: TInt64DynArray; Value: Int64): PtrInt; begin result := Int64ScanIndex(pointer(Values), Length(Values), Value); if result < 0 then result := AddInt64(Values, Value); end; procedure MakeUniqueArray(old: PDynArrayRec; ItemSizeShl: TDALen); var new: PDynArrayRec; n: PtrInt; begin dec(old); dec(old^.refCnt); n := (old^.length shl ItemSizeShl) + SizeOf(new^); new := AllocMem(n); MoveFast(old^, new^, n); // copy header + all ordinal values new^.refCnt := 1; end; procedure DeleteWord(var Values: TWordDynArray; Index: PtrInt); var n: PtrInt; begin n := Length(Values); if PtrUInt(Index) >= PtrUInt(n) then exit; // wrong Index dec(n); if n > Index then begin if PDACnt(PAnsiChar(Values) - _DACNT)^ > 1 then MakeUniqueArray(pointer(Values), {shl=}1); MoveFast(Values[Index + 1], Values[Index], (n - Index) * SizeOf(Word)); end; SetLength(Values, n); end; procedure DeleteInteger(var Values: TIntegerDynArray; Index: PtrInt); var n: PtrInt; begin n := Length(Values); if PtrUInt(Index) >= PtrUInt(n) then exit; // wrong Index dec(n); if n > Index then begin if PDACnt(PAnsiChar(Values) - _DACNT)^ > 1 then MakeUniqueArray(pointer(Values), {shl=}2); MoveFast(Values[Index + 1], Values[Index], (n - Index) * SizeOf(integer)); end; SetLength(Values, n); end; procedure DeleteInteger(var Values: TIntegerDynArray; var ValuesCount: integer; Index: PtrInt); var n: PtrInt; begin n := ValuesCount; if PtrUInt(Index) >= PtrUInt(n) then exit; // wrong Index dec(n, Index + 1); if n > 0 then begin if PDACnt(PAnsiChar(Values) - _DACNT)^ > 1 then MakeUniqueArray(pointer(Values), {shl=}2); MoveFast(Values[Index + 1], Values[Index], n * SizeOf(integer)); end; dec(ValuesCount); end; procedure DeleteInt64(var Values: TInt64DynArray; Index: PtrInt); var n: PtrInt; begin n := Length(Values); if PtrUInt(Index) >= PtrUInt(n) then exit; // wrong Index dec(n); if n > Index then begin if PDACnt(PAnsiChar(Values) - _DACNT)^ > 1 then MakeUniqueArray(pointer(Values), {shl=}3); MoveFast(Values[Index + 1], Values[Index], (n - Index) * SizeOf(Int64)); end; SetLength(Values, n); end; procedure DeleteInt64(var Values: TInt64DynArray; var ValuesCount: integer; Index: PtrInt); var n: PtrInt; begin n := ValuesCount; if PtrUInt(Index) >= PtrUInt(n) then exit; // wrong Index dec(n, Index + 1); if n > 0 then begin if PDACnt(PAnsiChar(Values) - _DACNT)^ > 1 then MakeUniqueArray(pointer(Values), {shl=}3); MoveFast(Values[Index + 1], Values[Index], n * SizeOf(Int64)); end; dec(ValuesCount); end; procedure FillIncreasing(Values: PIntegerArray; StartValue: integer; Count: PtrUInt); var i: PtrUInt; begin if Count > 0 then if StartValue = 0 then for i := 0 to Count - 1 do Values[i] := i else for i := 0 to Count - 1 do begin Values[i] := StartValue; inc(StartValue); end; end; procedure QuickSortInteger(ID: PIntegerArray; L, R: PtrInt); var I, J, P: PtrInt; tmp: integer; begin if L < R then repeat I := L; J := R; P := (L + R) shr 1; repeat tmp := ID[P]; if ID[I] < tmp then repeat inc(I) until ID[I] >= tmp; if ID[J] > tmp then repeat dec(J) until ID[J] <= tmp; if I <= J then begin tmp := ID[J]; ID[J] := ID[I]; ID[I] := tmp; if P = I then P := J else if P = J then P := I; inc(I); dec(J); end; until I > J; if J - L < R - I then begin // use recursion only for smaller range if L < J then QuickSortInteger(ID, L, J); L := I; end else begin if I < R then QuickSortInteger(ID, I, R); R := J; end; until L >= R; end; procedure QuickSortInteger(var ID: TIntegerDynArray); begin QuickSortInteger(pointer(ID), 0, high(ID)); end; procedure QuickSortInteger(ID, CoValues: PIntegerArray; L, R: PtrInt); var I, J, P: PtrInt; tmp: integer; begin if L < R then repeat I := L; J := R; P := (L + R) shr 1; repeat tmp := ID[P]; if ID[I] < tmp then repeat inc(I) until ID[I] >= tmp; if ID[J] > tmp then repeat dec(J) until ID[J] <= tmp; if I <= J then begin tmp := ID[J]; ID[J] := ID[I]; ID[I] := tmp; tmp := CoValues[J]; CoValues[J] := CoValues[I]; CoValues[I] := tmp; if P = I then P := J else if P = J then P := I; inc(I); dec(J); end; until I > J; if J - L < R - I then begin // use recursion only for smaller range if L < J then QuickSortInteger(ID, CoValues, L, J); L := I; end else begin if I < R then QuickSortInteger(ID, CoValues, I, R); R := J; end; until L >= R; end; procedure QuickSortWord(ID: PWordArray; L, R: PtrInt); var I, J, P: PtrInt; tmp: word; begin if L < R then repeat I := L; J := R; P := (L + R) shr 1; repeat tmp := ID[P]; if ID[I] < tmp then repeat inc(I) until ID[I] >= tmp; if ID[J] > tmp then repeat dec(J) until ID[J] <= tmp; if I <= J then begin tmp := ID[J]; ID[J] := ID[I]; ID[I] := tmp; if P = I then P := J else if P = J then P := I; inc(I); dec(J); end; until I > J; if J - L < R - I then begin // use recursion only for smaller range if L < J then QuickSortWord(ID, L, J); L := I; end else begin if I < R then QuickSortWord(ID, I, R); R := J; end; until L >= R; end; procedure QuickSortInt64(ID: PInt64Array; L, R: PtrInt); var I, J, P: PtrInt; tmp: Int64; begin if L < R then repeat I := L; J := R; P := (L + R) shr 1; repeat {$ifdef CPU64} tmp := ID^[P]; if ID[I] < tmp then repeat inc(I) until ID[I] >= tmp; if ID[J] > tmp then repeat dec(J) until ID[J] <= tmp; {$else} while ID[I] < ID[P] do inc(I); while ID[J] > ID[P] do dec(J); {$endif CPU64} if I <= J then begin tmp := ID[J]; ID[J] := ID[I]; ID[I] := tmp; if P = I then P := J else if P = J then P := I; inc(I); dec(J); end; until I > J; if J - L < R - I then begin // use recursion only for smaller range if L < J then QuickSortInt64(ID, L, J); L := I; end else begin if I < R then QuickSortInt64(ID, I, R); R := J; end; until L >= R; end; procedure QuickSortQWord(ID: PQWordArray; L, R: PtrInt); var I, J, P: PtrInt; tmp: QWord; begin if L < R then repeat I := L; J := R; P := (L + R) shr 1; repeat {$ifdef CPUX86} // circumvent QWord comparison slowness (and bug) while CompareQWord(ID[I], ID[P]) < 0 do inc(I); while CompareQWord(ID[J], ID[P]) > 0 do dec(J); {$else} tmp := ID[P]; if ID[I] < tmp then repeat inc(I) until ID[I] >= tmp; if ID[J] > tmp then repeat dec(J) until ID[J] <= tmp; {$endif CPUX86} if I <= J then begin tmp := ID[J]; ID[J] := ID[I]; ID[I] := tmp; if P = I then P := J else if P = J then P := I; inc(I); dec(J); end; until I > J; if J - L < R - I then begin // use recursion only for smaller range if L < J then QuickSortQWord(ID, L, J); L := I; end else begin if I < R then QuickSortQWord(ID, I, R); R := J; end; until L >= R; end; procedure QuickSortDouble(ID: PDoubleArray; L, R: PtrInt); var I, J, P: PtrInt; tmp: double; begin if L < R then repeat I := L; J := R; P := (L + R) shr 1; repeat tmp := ID[P]; while ID[I] < tmp do inc(I); while ID[J] > tmp do dec(J); if I <= J then begin tmp := ID[J]; ID[J] := ID[I]; ID[I] := tmp; if P = I then P := J else if P = J then P := I; inc(I); dec(J); end; until I > J; if J - L < R - I then begin // use recursion only for smaller range if L < J then QuickSortDouble(ID, L, J); L := I; end else begin if I < R then QuickSortDouble(ID, I, R); R := J; end; until L >= R; end; procedure QuickSortInt64(ID, CoValues: PInt64Array; L, R: PtrInt); var I, J, P: PtrInt; tmp: Int64; begin if L < R then repeat I := L; J := R; P := (L + R) shr 1; repeat {$ifdef CPU64} tmp := ID^[P]; if ID[I] < tmp then repeat inc(I) until ID[I] >= tmp; if ID[J] > tmp then repeat dec(J) until ID[J] <= tmp; {$else} while ID[I] < ID[P] do inc(I); while ID[J] > ID[P] do dec(J); {$endif CPU64} if I <= J then begin tmp := ID[J]; ID[J] := ID[I]; ID[I] := tmp; tmp := CoValues[J]; CoValues[J] := CoValues[I]; CoValues[I] := tmp; if P = I then P := J else if P = J then P := I; inc(I); dec(J); end; until I > J; if J - L < R - I then begin // use recursion only for smaller range if L < J then QuickSortInt64(ID, CoValues, L, J); L := I; end else begin if I < R then QuickSortInt64(ID, CoValues, I, R); R := J; end; until L >= R; end; function FastFindIntegerSorted(const Values: TIntegerDynArray; Value: integer): PtrInt; begin result := FastFindIntegerSorted(pointer(Values), Length(Values) - 1, Value); end; {$ifndef CPUX64} // x86_64 has fast branchless asm for those functions function FastFindWordSorted(P: PWordArray; R: PtrInt; Value: Word): PtrInt; var L, RR: PtrInt; cmp: integer; begin L := 0; if 0 <= R then repeat result := (L + R) shr 1; cmp := P^[result] - Value; if cmp = 0 then exit; RR := result + 1; // compile as 2 branchless cmovc/cmovnc on FPC dec(result); if cmp < 0 then L := RR else R := result; until L > R; result := -1 end; function FastFindIntegerSorted(P: PIntegerArray; R: PtrInt; Value: integer): PtrInt; var L, RR: PtrInt; cmp: integer; begin L := 0; if 0 <= R then repeat result := (L + R) shr 1; cmp := CompareInteger(P^[result], Value); if cmp = 0 then exit; RR := result + 1; // compile as 2 branchless cmovc/cmovnc on FPC dec(result); if cmp < 0 then L := RR else R := result; until L > R; result := -1 end; function FastFindInt64Sorted(P: PInt64Array; R: PtrInt; const Value: Int64): PtrInt; var L, RR: PtrInt; cmp: integer; begin L := 0; if 0 <= R then repeat result := (L + R) shr 1; cmp := CompareInt64(P^[result], Value); if cmp = 0 then exit; RR := result + 1; // compile as 2 branchless cmovc/cmovnc on FPC dec(result); if cmp < 0 then L := RR else R := result; until L > R; result := -1 end; {$endif CPUX64} function FastFindQWordSorted(P: PQWordArray; R: PtrInt; const Value: QWord): PtrInt; var L, RR: PtrInt; cmp: integer; begin L := 0; if 0 <= R then repeat result := (L + R) shr 1; cmp := CompareQWord(P^[result], Value); if cmp = 0 then exit; RR := result + 1; // compile as 2 branchless cmovc/cmovnc on FPC dec(result); if cmp < 0 then L := RR else R := result; until L > R; result := -1 end; function FastLocateIntegerSorted(P: PIntegerArray; R: PtrInt; Value: integer): PtrInt; var L, RR: PtrInt; cmp: integer; begin if R < 0 then result := 0 else begin L := 0; repeat result := (L + R) shr 1; cmp := P^[result] - Value; if cmp = 0 then begin result := -result - 1; // return -(foundindex+1) if already exists exit; end; RR := result + 1; // compile as 2 branchless cmovl/cmovge on FPC dec(result); if cmp < 0 then L := RR else R := result; until L > R; while (result >= 0) and (P^[result] >= Value) do dec(result); inc(result); // return the index where to insert end; end; function FastSearchIntegerSorted(P: PIntegerArray; R: PtrInt; Value: integer): PtrInt; var L, RR: PtrInt; cmp: integer; begin if R < 0 then result := 0 else begin L := 0; repeat result := (L + R) shr 1; cmp := P^[result] - Value; if cmp = 0 then exit; // return exact matching index RR := result + 1; // compile as 2 branchless cmovl/cmovge on FPC dec(result); if cmp < 0 then L := RR else R := result; until L > R; while (result >= 0) and (P^[result] >= Value) do dec(result); inc(result); // return the index where to insert end; end; function FastLocateWordSorted(P: PWordArray; R: integer; Value: word): PtrInt; var L, cmp: PtrInt; begin if R < 0 then result := 0 else begin L := 0; repeat result := (L + R) shr 1; cmp := P^[result] - Value; if cmp = 0 then begin result := -result - 1; // return -(foundindex+1) if already exists exit; end; if cmp < 0 then L := result + 1 else R := result - 1; until L > R; while (result >= 0) and (P^[result] >= Value) do dec(result); inc(result); // return the index where to insert end; end; function AddSortedInteger(var Values: TIntegerDynArray; var ValuesCount: integer; Value: integer; CoValues: PIntegerDynArray): PtrInt; begin result := FastLocateIntegerSorted(pointer(Values), ValuesCount - 1, Value); if result >= 0 then // if Value exists -> fails and return -(foundindex+1) result := InsertInteger(Values, ValuesCount, Value, result, CoValues); end; function AddSortedInteger(var Values: TIntegerDynArray; Value: integer; CoValues: PIntegerDynArray): PtrInt; var ValuesCount: integer; begin ValuesCount := Length(Values); result := FastLocateIntegerSorted(pointer(Values), ValuesCount - 1, Value); if result < 0 then exit; // Value exists -> fails and return -(foundindex+1) SetLength(Values, ValuesCount + 1); // manual size increase result := InsertInteger(Values, ValuesCount, Value, result, CoValues); end; function InsertInteger(var Values: TIntegerDynArray; var ValuesCount: integer; Value: integer; Index: PtrInt; CoValues: PIntegerDynArray): PtrInt; var n: PtrInt; begin result := Index; n := Length(Values); if ValuesCount = n then begin n := NextGrow(n); SetLength(Values, n); if CoValues <> nil then SetLength(CoValues^, n); end; n := ValuesCount; if PtrUInt(result) < PtrUInt(n) then begin n := (n - result) * SizeOf(integer); MoveFast(Values[result], Values[result + 1], n); if CoValues <> nil then MoveFast(CoValues^[result], CoValues^[result + 1], n); end else result := n; Values[result] := Value; inc(ValuesCount); end; function TIntegerDynArrayFrom(const Values: array of integer): TIntegerDynArray; var i: PtrInt; begin Finalize(result); SetLength(result, Length(Values)); for i := 0 to high(Values) do result[i] := Values[i]; end; function TIntegerDynArrayFrom64(const Values: TInt64DynArray; raiseExceptionOnOverflow: boolean): TIntegerDynArray; var i: PtrInt; const MinInt = -MaxInt - 1; begin Finalize(result); SetLength(result, Length(Values)); for i := 0 to Length(Values) - 1 do if Values[i] > MaxInt then if raiseExceptionOnOverflow then raise Exception.CreateFmt('TIntegerDynArrayFrom64: Values[%d]=%d>%d', [i, Values[i], MaxInt]) else result[i] := MaxInt else if Values[i] < MinInt then if raiseExceptionOnOverflow then raise Exception.CreateFmt('TIntegerDynArrayFrom64: Values[%d]=%d<%d', [i, Values[i], MinInt]) else result[i] := MinInt else result[i] := Values[i]; end; function TInt64DynArrayFrom(const Values: TIntegerDynArray): TInt64DynArray; var i: PtrInt; begin Finalize(result); SetLength(result, Length(Values)); for i := 0 to Length(Values) - 1 do result[i] := Values[i]; end; function TQWordDynArrayFrom(const Values: TCardinalDynArray): TQWordDynArray; var i: PtrInt; begin Finalize(result); SetLength(result, Length(Values)); for i := 0 to Length(Values) - 1 do result[i] := Values[i]; end; function FromI32(const Values: array of integer): TIntegerDynArray; var i: PtrInt; begin Finalize(result); SetLength(result, Length(Values)); for i := 0 to high(Values) do result[i] := Values[i]; end; function FromU32(const Values: array of cardinal): TCardinalDynArray; var i: PtrInt; begin Finalize(result); SetLength(result, Length(Values)); for i := 0 to high(Values) do result[i] := Values[i]; end; function FromI64(const Values: array of Int64): TInt64DynArray; var i: PtrInt; begin Finalize(result); SetLength(result, Length(Values)); for i := 0 to high(Values) do result[i] := Values[i]; end; function FromU64(const Values: array of QWord): TQWordDynArray; var i: PtrInt; begin Finalize(result); SetLength(result, Length(Values)); for i := 0 to high(Values) do result[i] := Values[i]; end; function gcd(a, b: PtrUInt): PtrUInt; begin result := 0; if a <> 0 then while b <> 0 do begin result := b; b := a mod b; a := result; end; end; { TSortedWordArray } function TSortedWordArray.Add(aValue: Word): PtrInt; begin result := Count; // optimistic check of perfectly increasing aValue if (result > 0) and (aValue <= Values[result - 1]) then result := FastLocateWordSorted(pointer(Values), result - 1, aValue); if result < 0 then // aValue already exists in Values[] -> fails exit; if Count = Length(Values) then SetLength(Values, NextGrow(Count)); if result < Count then MoveFast(Values[result], Values[result + 1], (Count - result) * SizeOf(word)) else result := Count; Values[result] := aValue; inc(Count); end; function TSortedWordArray.IndexOf(aValue: Word): PtrInt; begin result := FastFindWordSorted(pointer(Values), Count - 1, aValue); end; procedure TSortedWordArray.SetArray(out aValues: TWordDynArray); begin if Count = 0 then exit; DynArrayFakeLength(Values, Count); // no realloc needed aValues := Values; end; { TSortedIntegerArray } function TSortedIntegerArray.Add(aValue: integer): PtrInt; begin result := Count; // optimistic check of perfectly increasing aValue if (result > 0) and (aValue <= Values[result - 1]) then result := FastLocateIntegerSorted(pointer(Values), result - 1, aValue); if result < 0 then // aValue already exists in Values[] -> fails exit; if Count = Length(Values) then SetLength(Values, NextGrow(Count)); if result < Count then MoveFast(Values[result], Values[result + 1], (Count - result) * SizeOf(integer)) else result := Count; Values[result] := aValue; inc(Count); end; function TSortedIntegerArray.IndexOf(aValue: integer): PtrInt; begin result := FastFindIntegerSorted(pointer(Values), Count - 1, aValue); end; procedure TSortedIntegerArray.SetArray(out aValues: TIntegerDynArray); begin if Count = 0 then exit; DynArrayFakeLength(Values, Count); // no realloc needed aValues := Values; end; { ************ ObjArray PtrArray InterfaceArray Wrapper Functions } { PtrArr* wrapper functions } function PtrArrayAdd(var aPtrArray; aItem: pointer): integer; var a: TPointerDynArray absolute aPtrArray; begin result := length(a); SetLength(a, result + 1); a[result] := aItem; end; function PtrArrayAdd(var aPtrArray; aItem: pointer; var aPtrArrayCount: integer): PtrInt; var a: TPointerDynArray absolute aPtrArray; begin result := aPtrArrayCount; if result = length(a) then SetLength(a, NextGrow(result)); a[result] := aItem; inc(aPtrArrayCount); end; function PtrArrayAddOnce(var aPtrArray; aItem: pointer): PtrInt; var a: TPointerDynArray absolute aPtrArray; n: PtrInt; begin n := length(a); result := PtrUIntScanIndex(pointer(a), n, PtrUInt(aItem)); if result >= 0 then exit; SetLength(a, n + 1); a[n] := aItem; result := n; end; function PtrArrayAddOnce(var aPtrArray; aItem: pointer; var aPtrArrayCount: integer): PtrInt; begin result := PtrUIntScanIndex(pointer(aPtrArray), aPtrArrayCount, PtrUInt(aItem)); if result < 0 then result := PtrArrayAdd(aPtrArray, aItem, aPtrArrayCount); end; function PtrArrayInsert(var aPtrArray; aItem: pointer; aIndex: PtrInt; var aPtrArrayCount: integer): PtrInt; var a: TPointerDynArray absolute aPtrArray; n: PtrInt; begin n := aPtrArrayCount; if length(a) = n then SetLength(a, NextGrow(n)); if PtrUInt(aIndex) < PtrUInt(n) then MoveFast(a[aIndex], a[aIndex + 1], (n - aIndex) * SizeOf(pointer)) else aIndex := n; a[aIndex] := aItem; inc(aPtrArrayCount); result := aIndex; end; procedure PtrArrayDelete(var aPtrArray; aIndex: PtrInt; aCount: PInteger); var a: TPointerDynArray absolute aPtrArray; n: PtrInt; begin if aCount = nil then n := length(a) else n := aCount^; if PtrUInt(aIndex) >= PtrUInt(n) then exit; // out of range dec(n); if n > aIndex then MoveFast(a[aIndex + 1], a[aIndex], (n - aIndex) * SizeOf(pointer)); a[n] := nil; // better safe than sorry if aCount = nil then if n and 255 <> 0 then DynArrayFakeLength(a, n) // call ReallocMem() once every 256 deletes else SetLength(a, n) // finalize if n = 0 else begin aCount^ := n; if n = 0 then Finalize(a); end; end; function PtrArrayDelete(var aPtrArray; aItem: pointer; aCount: PInteger): PtrInt; var a: TPointerDynArray absolute aPtrArray; n: PtrInt; begin if aCount = nil then n := length(a) else n := aCount^; result := PtrUIntScanIndex(pointer(a), n, PtrUInt(aItem)); if result < 0 then exit; dec(n); if n > result then MoveFast(a[result + 1], a[result], (n - result) * SizeOf(pointer)); a[n] := nil; // better safe than sorry if aCount = nil then SetLength(a, n) else begin aCount^ := n; if n = 0 then Finalize(a); end; end; function PtrArrayFind(var aPtrArray; aItem: pointer): integer; var a: TPointerDynArray absolute aPtrArray; begin result := PtrUIntScanIndex(pointer(a), length(a), PtrUInt(aItem)); end; { wrapper functions to T*ObjArr types } function ObjArrayAdd(var aObjArray; aItem: TObject): PtrInt; begin result := PtrArrayAdd(aObjArray, aItem); end; function ObjArrayAddCount(var aObjArray; aItem: TObject; var aObjArrayCount: integer): PtrInt; begin result := PtrArrayAdd(aObjArray, aItem, aObjArrayCount); end; function ObjArrayAddFrom(var aDestObjArray; const aSourceObjArray): PtrInt; var n: PtrInt; s: TObjectDynArray absolute aSourceObjArray; d: TObjectDynArray absolute aDestObjArray; begin result := length(d); n := length(s); SetLength(d, result + n); MoveFast(s[0], d[result], n * SizeOf(pointer)); inc(result, n); end; function ObjArrayAppend(var aDestObjArray, aSourceObjArray): PtrInt; begin result := ObjArrayAddFrom(aDestObjArray, aSourceObjArray); TObjectDynArray(aSourceObjArray) := nil; // aSourceObjArray[] changed ownership end; function ObjArrayAddOnce(var aObjArray; aItem: TObject): PtrInt; begin result := PtrArrayAddOnce(aObjArray, aItem); end; function ObjArrayAddOnce(var aObjArray; aItem: TObject; var aObjArrayCount: integer): PtrInt; begin result := PtrArrayAddOnce(aObjArray, aItem, aObjArrayCount); end; function ObjArrayAddOnceFrom(var aDestObjArray; const aSourceObjArray): PtrInt; var n, i: PtrInt; s: TObjectDynArray absolute aSourceObjArray; d: TObjectDynArray absolute aDestObjArray; begin result := length(d); n := length(s); if n = 0 then exit; SetLength(d, result + n); for i := 0 to n - 1 do if not PtrUIntScanExists(pointer(d), result, PtrUInt(s[i])) then begin d[result] := s[i]; inc(result); end; DynArrayFakeLength(d, result); end; procedure ObjArraySetLength(var aObjArray; aLength: integer); begin SetLength(TObjectDynArray(aObjArray), aLength); end; function ObjArrayFind(const aObjArray; aItem: TObject): PtrInt; begin result := PtrUIntScanIndex( pointer(aObjArray), length(TObjectDynArray(aObjArray)), PtrUInt(aItem)); end; function ObjArrayFind(const aObjArray; aCount: integer; aItem: TObject): PtrInt; begin result := PtrUIntScanIndex(pointer(aObjArray), aCount, PtrUInt(aItem)); end; function ObjArrayNotNilCount(const aObjArray): integer; var i: PtrInt; a: TObjectDynArray absolute aObjArray; begin result := 0; for i := 0 to length(a) - 1 do inc(result, ord(a[i] <> nil)); end; procedure ObjArrayDelete(var aObjArray; aItemIndex: PtrInt; aContinueOnException: boolean; aCount: PInteger); var n: PtrInt; a: TObjectDynArray absolute aObjArray; begin if aCount = nil then n := length(a) else n := aCount^; if cardinal(aItemIndex) >= cardinal(n) then exit; // out of range if aContinueOnException then try a[aItemIndex].Free; except end else a[aItemIndex].Free; dec(n); if n > aItemIndex then MoveFast(a[aItemIndex + 1], a[aItemIndex], (n - aItemIndex) * SizeOf(TObject)); if aCount = nil then if n = 0 then Finalize(a) else DynArrayFakeLength(a, n) else aCount^ := n; end; function ObjArrayDelete(var aObjArray; aItem: TObject): PtrInt; begin result := PtrUIntScanIndex(pointer(aObjArray), length(TObjectDynArray(aObjArray)), PtrUInt(aItem)); if result >= 0 then ObjArrayDelete(aObjArray, result); end; function ObjArrayDelete(var aObjArray; aCount: integer; aItem: TObject): PtrInt; overload; begin result := PtrUIntScanIndex(pointer(aObjArray), aCount, PtrUInt(aItem)); if result >= 0 then ObjArrayDelete(aObjArray, result, false, @aCount); end; procedure RawObjectsClear(o: PObject; n: integer); var obj: TObject; begin if n > 0 then repeat obj := o^; if obj <> nil then begin // inlined FreeAndNil(o^) o^ := nil; obj.Destroy; end; inc(o); dec(n); until n = 0; end; procedure FreeAndNilSafe(var aObj); begin if TObject(aObj) = nil then exit; try // slower but paranoidically safe TObject(aObj).Destroy; except end; TObject(aObj) := nil; // we could do it AFTER destroy end; procedure InterfaceNilSafe(var aInterface); begin if IInterface(aInterface) <> nil then try // slower but paranoidically safe IInterface(aInterface) := nil; except pointer(aInterface) := nil; // force variable to nil end; end; procedure InterfacesNilSafe(const aInterfaces: array of pointer); var i: PtrInt; begin for i := 0 to high(aInterfaces) do InterfaceNilSafe(aInterfaces[i]^); end; procedure ObjArrayClear(var aObjArray); var a: TObjectDynArray absolute aObjArray; begin if a = nil then exit; // release all owned TObject instances RawObjectsClear(pointer(aObjArray), PDALen(PAnsiChar(a) - _DALEN)^ + _DAOFF); // release the dynamic array itself a := nil; end; procedure ObjArrayClear(var aObjArray; aCount: integer); var a: TObjectDynArray absolute aObjArray; n: integer; begin n := length(a); if n = 0 then exit; if n < aCount then aCount := n; RawObjectsClear(pointer(aObjArray), aCount); a := nil; end; procedure ObjArrayClear(var aObjArray; aContinueOnException: boolean; aCount: PInteger); var n, i: PtrInt; a: TObjectDynArray absolute aObjArray; begin if aCount = nil then n := length(a) else begin n := aCount^; aCount^ := 0; end; if n = 0 then exit; if aContinueOnException then for i := n - 1 downto 0 do try a[i].Free; except end else RawObjectsClear(pointer(a), n); a := nil; // finalize the dynamic array itself end; procedure ObjArrayObjArrayClear(var aObjArray); var i: PtrInt; a: TPointerDynArray absolute aObjArray; begin if a <> nil then begin for i := 0 to length(a) - 1 do ObjArrayClear(a[i]); a := nil; end; end; procedure ObjArraysClear(const aObjArray: array of pointer); var i: PtrInt; begin for i := 0 to high(aObjArray) do if aObjArray[i] <> nil then ObjArrayClear(aObjArray[i]^); end; { wrapper functions to array of interface types } function InterfaceArrayAdd(var aInterfaceArray; const aItem: IUnknown): PtrInt; var a: TInterfaceDynArray absolute aInterfaceArray; begin result := length(a); SetLength(a, result + 1); a[result] := aItem; end; function InterfaceArrayAddCount(var aInterfaceArray; var aCount: integer; const aItem: IUnknown): PtrInt; var a: TInterfaceDynArray absolute aInterfaceArray; begin result := aCount; if result = length(a) then SetLength(a, NextGrow(result)); a[result] := aItem; inc(aCount); end; procedure InterfaceArrayAddOnce(var aInterfaceArray; const aItem: IUnknown); var a: TInterfaceDynArray absolute aInterfaceArray; n: PtrInt; begin if PtrUIntScanExists(pointer(aInterfaceArray), length(TInterfaceDynArray(aInterfaceArray)), PtrUInt(aItem)) then exit; n := length(a); SetLength(a, n + 1); a[n] := aItem; end; function InterfaceArrayFind(const aInterfaceArray; const aItem: IUnknown): PtrInt; begin result := PtrUIntScanIndex(pointer(aInterfaceArray), length(TInterfaceDynArray(aInterfaceArray)), PtrUInt(aItem)); end; procedure InterfaceArrayDelete(var aInterfaceArray; aItemIndex: PtrInt); var n: PtrInt; a: TInterfaceDynArray absolute aInterfaceArray; begin n := length(a); if PtrUInt(aItemIndex) >= PtrUInt(n) then exit; // out of range a[aItemIndex] := nil; dec(n); if n > aItemIndex then MoveFast(a[aItemIndex + 1], a[aItemIndex], (n - aItemIndex) * SizeOf(IInterface)); TPointerDynArray(aInterfaceArray)[n] := nil; // avoid GPF in SetLength() if n = 0 then Finalize(a) else DynArrayFakeLength(a, n); end; function InterfaceArrayDelete(var aInterfaceArray; const aItem: IUnknown): PtrInt; begin result := InterfaceArrayFind(aInterfaceArray, aItem); if result >= 0 then InterfaceArrayDelete(aInterfaceArray, result); end; { ************ low-level types mapping binary structures } function IsZero(const dig: THash128): boolean; var a: TPtrIntArray absolute dig; begin result := a[0] or a[1] {$ifdef CPU32} or a[2] or a[3]{$endif} = 0; end; function IsEqual(const A, B: THash128): boolean; var a_: TPtrIntArray absolute A; b_: TPtrIntArray absolute B; begin // uses anti-forensic time constant "xor/or" pattern result := ((a_[0] xor b_[0]) or (a_[1] xor b_[1]) {$ifdef CPU32} or (a_[2] xor b_[2]) or (a_[3] xor b_[3]) {$endif} ) = 0; end; procedure FillZero(out dig: THash128); var d: TInt64Array absolute dig; begin d[0] := 0; d[1] := 0; end; {$ifdef CPU64} function Hash128Index(P: PHash128Rec; Count: integer; h: PHash128Rec): integer; var _0, _1: PtrInt; // is likely to use CPU registers begin if P <> nil then begin _0 := h^.Lo; _1 := h^.Hi; for result := 0 to Count - 1 do if (P^.Lo = _0) and (P^.Hi = _1) then exit else inc(P); end; result := -1; // not found end; function Hash256Index(P: PHash256Rec; Count: integer; h: PHash256Rec): integer; var _0, _1: PtrInt; begin if P <> nil then begin _0 := h^.d0; _1 := h^.d1; for result := 0 to Count - 1 do if (P^.d0 = _0) and (P^.d1 = _1) and (P^.d2 = h^.d2) and (P^.d3 = h^.d3) then exit else inc(P); end; result := -1; // not found end; {$else} function Hash128Index(P: PHash128Rec; Count: integer; h: PHash128Rec): integer; begin if P <> nil then for result := 0 to Count - 1 do if (P^.i0 = h^.i0) and (P^.i1 = h^.i1) and (P^.i2 = h^.i2) and (P^.i3 = h^.i3) then exit else inc(P); result := -1; // not found end; function Hash256Index(P: PHash256Rec; Count: integer; h: PHash256Rec): integer; begin if P <> nil then for result := 0 to Count - 1 do if (P^.i0 = h^.i0) and (P^.i1 = h^.i1) and (P^.i2 = h^.i2) and (P^.i3 = h^.i3) and (P^.i4 = h^.i4) and (P^.i5 = h^.i5) and (P^.i6 = h^.i6) and (P^.i7 = h^.i7) then exit else inc(P); result := -1; // not found end; {$endif CPU64} function AddHash128(var Arr: THash128DynArray; const V: THash128; var Count: integer): PtrInt; begin result := Count; if result = length(Arr) then SetLength(Arr, NextGrow(result)); Arr[result] := V; inc(Count); end; function IsZero(const dig: THash160): boolean; var a: TIntegerArray absolute dig; begin result := a[0] or a[1] or a[2] or a[3] or a[4] = 0; end; function IsEqual(const A, B: THash160): boolean; var a_: TIntegerArray absolute A; b_: TIntegerArray absolute B; begin // uses anti-forensic time constant "xor/or" pattern result := ((a_[0] xor b_[0]) or (a_[1] xor b_[1]) or (a_[2] xor b_[2]) or (a_[3] xor b_[3]) or (a_[4] xor b_[4])) = 0; end; procedure FillZero(out dig: THash160); begin PInt64Array(@dig)^[0] := 0; PInt64Array(@dig)^[1] := 0; PIntegerArray(@dig)^[4] := 0; end; function IsZero(const dig: THash256): boolean; var a: TPtrIntArray absolute dig; begin result := a[0] or a[1] or a[2] or a[3] {$ifdef CPU32} or a[4] or a[5] or a[6] or a[7] {$endif} = 0; end; function IsEqual(const A, B: THash256): boolean; var a_: TPtrIntArray absolute A; b_: TPtrIntArray absolute B; begin // uses anti-forensic time constant "xor/or" pattern result := ((a_[0] xor b_[0]) or (a_[1] xor b_[1]) or (a_[2] xor b_[2]) or (a_[3] xor b_[3]) {$ifdef CPU32} or (a_[4] xor b_[4]) or (a_[5] xor b_[5]) or (a_[6] xor b_[6]) or (a_[7] xor b_[7]) {$endif} ) = 0; end; procedure FillZero(out dig: THash256); var d: TInt64Array absolute dig; begin d[0] := 0; d[1] := 0; d[2] := 0; d[3] := 0; end; function IsZero(const dig: THash384): boolean; var a: TPtrIntArray absolute dig; begin result := a[0] or a[1] or a[2] or a[3] or a[4] or a[5] {$ifdef CPU32} or a[6] or a[7] or a[8] or a[9] or a[10] or a[11] {$endif} = 0; end; function IsEqual(const A, B: THash384): boolean; var a_: TPtrIntArray absolute A; b_: TPtrIntArray absolute B; begin // uses anti-forensic time constant "xor/or" pattern result := ((a_[0] xor b_[0]) or (a_[1] xor b_[1]) or (a_[2] xor b_[2]) or (a_[3] xor b_[3]) or (a_[4] xor b_[4]) or (a_[5] xor b_[5]) {$ifdef CPU32} or (a_[6] xor b_[6]) or (a_[7] xor b_[7]) or (a_[8] xor b_[8]) or (a_[9] xor b_[9]) or (a_[10] xor b_[10]) or (a_[11] xor b_[11]) {$endif}) = 0; end; procedure FillZero(out dig: THash384); var d: TInt64Array absolute dig; begin d[0] := 0; d[1] := 0; d[2] := 0; d[3] := 0; d[4] := 0; d[5] := 0; end; function IsZero(const dig: THash512): boolean; var a: TPtrIntArray absolute dig; begin result := a[0] or a[1] or a[2] or a[3] or a[4] or a[5] or a[6] or a[7] {$ifdef CPU32} or a[8] or a[9] or a[10] or a[11] or a[12] or a[13] or a[14] or a[15] {$endif} = 0; end; function IsEqual(const A, B: THash512): boolean; var a_: TPtrIntArray absolute A; b_: TPtrIntArray absolute B; begin // uses anti-forensic time constant "xor/or" pattern result := ((a_[0] xor b_[0]) or (a_[1] xor b_[1]) or (a_[2] xor b_[2]) or (a_[3] xor b_[3]) or (a_[4] xor b_[4]) or (a_[5] xor b_[5]) or (a_[6] xor b_[6]) or (a_[7] xor b_[7]) {$ifdef CPU32} or (a_[8] xor b_[8]) or (a_[9] xor b_[9]) or (a_[10] xor b_[10]) or (a_[11] xor b_[11]) or (a_[12] xor b_[12]) or (a_[13] xor b_[13]) or (a_[14] xor b_[14]) or (a_[15] xor b_[15]) {$endif}) = 0; end; procedure FillZero(out dig: THash512); var d: TInt64Array absolute dig; begin d[0] := 0; d[1] := 0; d[2] := 0; d[3] := 0; d[4] := 0; d[5] := 0; d[6] := 0; d[7] := 0; end; function IsEqual(const A, B; count: PtrInt): boolean; var perbyte: boolean; // ensure no optimization takes place begin result := true; while count > 0 do begin dec(count); perbyte := PByteArray(@A)[count] = PByteArray(@B)[count]; result := result and perbyte; end; end; {$ifdef ISDELPHI} // intrinsic in FPC {$ifdef CPUINTEL} procedure ReadBarrier; asm {$ifdef CPUX86} lock add dword ptr [esp], 0 {$else} .noframe lfence // lfence requires an SSE CPU, which is OK on x86-64 {$endif CPUX86} end; {$else} procedure ReadBarrier; begin MemoryBarrier; // modern Delphi intrinsic end; {$endif CPUINTEL} {$endif ISDELPHI} procedure Rcu32(var src, dst); begin repeat integer(dst) := integer(src); ReadBarrier; until integer(dst) = integer(src); end; procedure Rcu64(var src, dst); begin repeat Int64(dst) := Int64(src); ReadBarrier; until Int64(dst) = Int64(src); end; procedure RcuPtr(var src, dst); begin repeat PtrInt(dst) := PtrInt(src); ReadBarrier; until PtrInt(dst) = PtrInt(src); end; procedure Rcu128(var src, dst); var s: THash128Rec absolute src; d: THash128Rec absolute dst; begin repeat d := s; ReadBarrier; until (d.L = s.L) and (d.H = s.H); end; procedure Rcu(var src, dst; len: integer); begin if len > 0 then repeat MoveByOne(@src, @dst, len); // per-byte inlined copy ReadBarrier; until CompareMemSmall(@src, @dst, len); end; { ************ low-level functions manipulating bits } // naive code gives the best performance - bts [Bits] has an overhead // we tried with PPtrIntArray but PIntegerArray seems to generate better code function GetBit(const Bits; aIndex: PtrInt): boolean; begin result := TIntegerArray(Bits)[aIndex shr 5] and (1 shl (aIndex and 31)) <> 0; end; procedure SetBit(var Bits; aIndex: PtrInt); begin TIntegerArray(Bits)[aIndex shr 5] := TIntegerArray(Bits)[aIndex shr 5] or (1 shl (aIndex and 31)); end; procedure UnSetBit(var Bits; aIndex: PtrInt); begin PIntegerArray(@Bits)^[aIndex shr 5] := PIntegerArray(@Bits)^[aIndex shr 5] and not (1 shl (aIndex and 31)); end; function GetBitPtr(Bits: pointer; aIndex: PtrInt): boolean; begin result := PIntegerArray(Bits)[aIndex shr 5] and (1 shl (aIndex and 31)) <> 0; end; procedure SetBitPtr(Bits: pointer; aIndex: PtrInt); begin PIntegerArray(Bits)[aIndex shr 5] := PIntegerArray(Bits)[aIndex shr 5] or (1 shl (aIndex and 31)); end; procedure UnSetBitPtr(Bits: pointer; aIndex: PtrInt); begin PIntegerArray(Bits)^[aIndex shr 5] := PIntegerArray(Bits)^[aIndex shr 5] and not (1 shl (aIndex and 31)); end; function GetBit64(const Bits: Int64; aIndex: PtrInt): boolean; begin result := byte(aIndex) in TBits64(Bits); end; procedure SetBit64(var Bits: Int64; aIndex: PtrInt); begin include(PBits64(@Bits)^, aIndex); end; procedure UnSetBit64(var Bits: Int64; aIndex: PtrInt); begin exclude(PBits64(@Bits)^, aIndex); end; function GetBitsCount(const Bits; Count: PtrInt): PtrInt; var P: PPtrInt; popcnt: function(value: PtrInt): PtrInt; // fast redirection within loop begin P := @Bits; result := 0; popcnt := @GetBitsCountPtrInt; if Count >= POINTERBITS then repeat dec(Count, POINTERBITS); inc(result, popcnt(P^)); // use SSE4.2 if available inc(P); until Count < POINTERBITS; if Count > 0 then inc(result, popcnt(P^ and ((PtrInt(1) shl Count) - 1))); end; function GetAllBits(Bits, BitCount: cardinal): boolean; begin if (BitCount >= low(ALLBITS_CARDINAL)) and (BitCount <= high(ALLBITS_CARDINAL)) then begin BitCount := ALLBITS_CARDINAL[BitCount]; result := (Bits and BitCount) = BitCount; end else result := false; end; function BitsToBytes(bits: byte): byte; begin result := (bits + 7) shr 3; end; { ************ Faster alternative to RTL standard functions } {$ifndef CPUX86} // those functions have their own PIC-compatible x86 asm version function StrLenSafe(S: pointer): PtrInt; begin result := PtrUInt(S); if S <> nil then repeat if PAnsiChar(result)[0] <> #0 then if PAnsiChar(result)[1] <> #0 then if PAnsiChar(result)[2] <> #0 then if PAnsiChar(result)[3] <> #0 then begin inc(result, 4); continue; end else begin dec(result, PtrUInt(S) - 3); exit; end else begin dec(result, PtrUInt(S) - 2); exit; end else dec(PtrUInt(S)); dec(result, PtrUInt(S)); exit; until false; end; function StrComp(Str1, Str2: pointer): PtrInt; var c: byte; begin result := 0; if Str1 <> nil then if Str2 <> nil then begin dec(PtrUInt(Str1), PtrUInt(Str2)); if Str1 = nil then exit; // Str1=Str2 repeat c := PByteArray(Str1)[PtrUInt(Str2)]; if c <> PByte(Str2)^ then break else if c = 0 then exit // Str1 = Str2 else inc(PByte(Str2)); until false; result := PByteArray(Str1)[PtrUInt(Str2)] - PByte(Str2)^; exit; end else inc(result) // Str2='' else if Str2 <> nil then dec(result); // Str1='' end; // from A. Sharahov's PosEx_Sha_Pas_2() - refactored for cross-platform/compiler function PosExPas(pSub, p: PUtf8Char; Offset: PtrUInt): PtrInt; var len, lenSub: PtrInt; ch: AnsiChar; pStart, pStop: PUtf8Char; label s2, s6, tt, t0, t1, t2, t3, t4, s0, s1, fnd, quit; begin result := 0; if (p = nil) or (pSub = nil) or (PtrInt(Offset) <= 0) then goto quit; len := PStrLen(p - _STRLEN)^; lenSub := PStrLen(pSub - _STRLEN)^ - 1; if (len < lenSub + PtrInt(Offset)) or (lenSub < 0) then goto quit; pStop := p + len; inc(p, lenSub); inc(pSub, lenSub); pStart := p; p := @p[Offset + 3]; ch := pSub[0]; lenSub := -lenSub; if p < pStop then goto s6; dec(p, 4); goto s2; s6: // check 6 chars per loop iteration if ch = p[-4] then goto t4; if ch = p[-3] then goto t3; if ch = p[-2] then goto t2; if ch = p[-1] then goto t1; s2:if ch = p[0] then goto t0; s1:if ch = p[1] then goto tt; s0:inc(p, 6); if p < pStop then goto s6; dec(p, 4); if p >= pStop then goto quit; goto s2; t4:dec(p, 2); t2:dec(p, 2); goto t0; t3:dec(p, 2); t1:dec(p, 2); tt:len := lenSub; if lenSub <> 0 then repeat if (pSub[len] <> p[len + 1]) or (pSub[len + 1] <> p[len + 2]) then goto s0; inc(len, 2); until len >= 0; inc(p, 2); if p <= pStop then goto fnd; goto quit; t0:len := lenSub; if lenSub <> 0 then repeat if (pSub[len] <> p[len]) or (pSub[len + 1] <> p[len + 1]) then goto s1; inc(len, 2); until len >= 0; inc(p); fnd: result := p - pStart; quit: end; function PosEx(const SubStr, S: RawUtf8; Offset: PtrUInt): PtrInt; begin result := PosExPas(pointer(SubStr), pointer(S), Offset); // inlined call end; {$endif CPUX86} function StrCompW(Str1, Str2: PWideChar): PtrInt; var c: word; begin result := 0; if Str1 <> Str2 then if Str1 <> nil then if Str2 <> nil then begin repeat c := PWord(Str1)^; if c <> PWord(Str2)^ then break else if c = 0 then exit; // Str1 = Str2 inc(Str1); inc(Str2); until false; result := PWord(Str1)^ - PWord(Str2)^; end else inc(result) // Str2='' else dec(result); // Str1='' end; function PosExChar(Chr: AnsiChar; const Str: RawUtf8): PtrInt; begin if Str <> '' then result := ByteScanIndex(pointer(Str), PStrLen(PtrUInt(Str) - _STRLEN)^, byte(Chr)) + 1 else result := 0; end; function PosChar(Str: PUtf8Char; StrLen: PtrInt; Chr: AnsiChar): PUtf8Char; begin if StrLen <> 0 then begin StrLen := ByteScanIndex(pointer(Str), StrLen, byte(Chr)); if StrLen >= 0 then result := Str + StrLen else result := nil; end else result := nil; end; {$ifdef UNICODE} function PosExString(const SubStr, S: string; Offset: PtrUInt): PtrInt; begin result := PosExStringPas(pointer(SubStr), pointer(S), Offset); end; function PosExStringPas(pSub, p: PChar; Offset: PtrUInt): PtrInt; var len, lenSub: PtrInt; ch: char; pStart, pStop: PChar; label Loop2, Loop6, TestT, Test0, Test1, Test2, Test3, Test4, AfterTestT, AfterTest0, Ret, Exit; begin result := 0; if (p = nil) or (pSub = nil) or (PtrInt(Offset) <= 0) then goto Exit; len := PStrLen(PtrUInt(p) - _STRLEN)^; lenSub := PStrLen(PtrUInt(pSub) - _STRLEN)^ - 1; if (len < lenSub + PtrInt(Offset)) or (lenSub < 0) then goto Exit; pStop := p + len; inc(p, lenSub); inc(pSub, lenSub); pStart := p; inc(p, Offset + 3); ch := pSub[0]; lenSub := -lenSub; if p < pStop then goto Loop6; dec(p, 4); goto Loop2; Loop6: // check 6 chars per loop iteration if ch = p[-4] then goto Test4; if ch = p[-3] then goto Test3; if ch = p[-2] then goto Test2; if ch = p[-1] then goto Test1; Loop2: if ch = p[0] then goto Test0; AfterTest0: if ch = p[1] then goto TestT; AfterTestT: inc(p, 6); if p < pStop then goto Loop6; dec(p, 4); if p >= pStop then goto Exit; goto Loop2; Test4: dec(p, 2); Test2: dec(p, 2); goto Test0; Test3: dec(p, 2); Test1: dec(p, 2); TestT: len := lenSub; if lenSub <> 0 then repeat if (pSub[len] <> p[len + 1]) or (pSub[len + 1] <> p[len + 2]) then goto AfterTestT; inc(len, 2); until len >= 0; inc(p, 2); if p <= pStop then goto Ret; goto Exit; Test0: len := lenSub; if lenSub <> 0 then repeat if (pSub[len] <> p[len]) or (pSub[len + 1] <> p[len + 1]) then goto AfterTest0; inc(len, 2); until len >= 0; inc(p); Ret: result := p - pStart; Exit: end; {$else} function PosExString(const SubStr, S: string; Offset: PtrUInt): PtrInt; begin {$ifdef CPUX86} result := PosEx(SubStr, S, Offset); // call x86 asm {$else} result := PosExPas(pointer(SubStr), pointer(S), Offset); {$endif CPUX86} end; {$endif UNICODE} function TrimU(const S: RawUtf8): RawUtf8; var i, L: PtrInt; begin L := Length(S); i := 1; while (i <= L) and (S[i] <= ' ') do inc(i); if i > L then FastAssignNew(result) // void string else if (i = 1) and (S[L] > ' ') then result := S // nothing to trim: reference counted copy else begin while S[L] <= ' ' do dec(L); dec(i); FastSetString(result, @PByteArray(S)[i], L - i); // trim and allocate end; end; procedure TrimSelf(var S: RawUtf8); var i, L: PtrInt; begin if S = '' then exit; L := PStrLen(PAnsiChar(pointer(S)) - _STRLEN)^; i := 1; while (i <= L) and (S[i] <= ' ') do inc(i); if i > L then FastAssignNew(S) // void string else if (i = 1) and (S[L] > ' ') then exit // nothing to trim else begin // trim the UTF-8 string while S[L] <= ' ' do dec(L); dec(i); dec(L, i); if (L <> 0) and (PStrCnt(PAnsiChar(pointer(S)) - _STRCNT)^ = 1) then begin if i <> 0 then MoveFast(PByteArray(S)[i], pointer(S)^, L); // trim left: move in place FakeLength(S, L); // after move, to properly set ending #0 end else FastSetString(S, @PByteArray(S)[i], L); // allocate end; end; {$ifndef PUREMORMOT2} function Trim(const S: RawUtf8): RawUtf8; begin result := TrimU(S); end; {$endif PUREMORMOT2} procedure TrimCopy(const S: RawUtf8; start, count: PtrInt; var result: RawUtf8); // faster alternative to TrimU(copy()) var L: PtrInt; begin if count > 0 then begin if start <= 0 then start := 1; L := Length(S); while (start <= L) and (S[start] <= ' ') do begin inc(start); dec(count); end; dec(start); dec(L,start); if count < L then L := count; while L > 0 do if S[start + L] <= ' ' then dec(L) else break; if L > 0 then begin FastSetString(result, @PByteArray(S)[start], L); exit; end; end; result := ''; end; function Split(const Str, SepStr: RawUtf8; StartPos: PtrInt): RawUtf8; var len, i: PtrInt; begin len := length(Str); if len = 0 then begin result := ''; exit; end; if StartPos > len then StartPos := len else if StartPos <= 0 then StartPos := 1; if (length(SepStr) = 1) and (StartPos <= 1) then i := PosExChar(SepStr[1], Str) // may use SSE2 on i386/x86_64 else i := PosEx(SepStr, Str, StartPos); if i > 0 then FastSetString(result, @PByteArray(Str)[StartPos - 1], i - StartPos) else if StartPos = 1 then result := Str else FastSetString(result, @PByteArray(Str)[StartPos - 1], len - StartPos + 1); end; function StrLenW(S: PWideChar): PtrInt; begin result := 0; if S <> nil then while true do if S[result + 0] <> #0 then if S[result + 1] <> #0 then if S[result + 2] <> #0 then if S[result + 3] <> #0 then inc(result, 4) else begin inc(result, 3); exit; end else begin inc(result, 2); exit; end else begin inc(result); exit; end else exit; end; function GotoNextControlChar(source: PUtf8Char): PUtf8Char; label _1, _2, _3; // ugly but faster begin result := source; repeat if result[0] < #13 then exit else if result[1] < #13 then goto _1 else if result[2] < #13 then goto _2 else if result[3] < #13 then goto _3 else begin inc(result, 4); continue; end; _3: inc(result); _2: inc(result); _1: inc(result); exit; until false; end; function GotoNextLine(source: PUtf8Char): PUtf8Char; label _0, _1, _2, _3; // ugly but faster begin repeat if source[0] < #13 then goto _0 else if source[1] < #13 then goto _1 else if source[2] < #13 then goto _2 else if source[3] < #13 then goto _3 else begin inc(source, 4); continue; end; _3: inc(source); _2: inc(source); _1: inc(source); _0: if source[0] = #13 then begin if source[1] = #10 then begin result := source + 2; // most common case is text ending with #13#10 exit; end; end else if source[0] = #0 then begin result := nil; // premature ending exit; end else if source[0] <> #10 then begin inc(source); continue; // e.g. #9 end; result := source + 1; exit; until false; end; function IsAnsiCompatible(PC: PAnsiChar): boolean; begin result := false; if PC <> nil then while true do if PC^ = #0 then break else if PC^ <= #127 then // 7-bit chars are always OK, whatever codepage/charset is used inc(PC) else exit; result := true; end; function IsAnsiCompatible(PC: PAnsiChar; Len: PtrUInt): boolean; begin if PC <> nil then begin result := false; Len := PtrUInt(@PC[Len - 4]); if Len >= PtrUInt(PC) then repeat if PCardinal(PC)^ and $80808080 <> 0 then exit; inc(PC, 4); until Len < PtrUInt(PC); inc(Len, 4); if Len > PtrUInt(PC) then repeat if PC^ > #127 then exit; inc(PC); until Len <= PtrUInt(PC); end; result := true; end; function IsAnsiCompatible(const Text: RawByteString): boolean; begin result := IsAnsiCompatible(PAnsiChar(pointer(Text)), Length(Text)); end; function IsAnsiCompatibleW(PW: PWideChar): boolean; begin result := false; if PW <> nil then while true do if ord(PW^) = 0 then break else if ord(PW^) <= 127 then inc(PW) else // 7-bit chars are always OK, whatever codepage/charset is used exit; result := true; end; function IsAnsiCompatibleW(PW: PWideChar; Len: PtrInt): boolean; begin result := false; if (PW <> nil) and (Len > 0) then repeat if ord(PW^) > 127 then exit; inc(PW); dec(Len); until Len = 0; result := true; end; procedure StrCntAdd(var refcnt: TStrCnt; increment: TStrCnt); begin {$ifdef STRCNT32} LockedAdd32(cardinal(refcnt), increment); {$else} LockedAdd(PtrUInt(refcnt), increment); {$endif STRCNT32} end; procedure DACntAdd(var refcnt: TDACnt; increment: TDACnt); begin {$ifdef DACNT32} LockedAdd32(cardinal(refcnt), increment); {$else} LockedAdd(PtrUInt(refcnt), increment); {$endif DACNT32} end; procedure FillZero(var dest; count: PtrInt); begin FillCharFast(dest, count, 0); end; procedure MoveAndZero(Source, Dest: Pointer; Count: PtrUInt); begin if Count = 0 then exit; MoveFast(Source^, Dest^, Count); FillCharFast(Source^, Count, 0); end; procedure FillZeroSmall(P: pointer; Length: PtrInt); begin inc(PtrUInt(P), PtrUInt(Length)); Length := -Length; repeat PByteArray(P)[Length] := 0; inc(Length); until Length = 0; end; threadvar // do not publish for compilation within Delphi packages _Lecuyer: TLecuyer; // uses only 16 bytes per thread function Lecuyer: PLecuyer; begin result := @_Lecuyer; end; {$ifdef OSDARWIN} // FPC CreateGuid calls /dev/urandom which is not advised function mach_absolute_time: Int64; cdecl external 'c'; function mach_continuous_time: Int64; cdecl external 'c'; procedure CreateGuid(var guid: TGuid); // sysutils version is slow begin PInt64Array(@guid)^[0] := mach_absolute_time; // monotonic time (in ns) PInt64Array(@guid)^[1] := mach_continuous_time; crc128c(@guid, SizeOf(guid), THash128(guid)); // good enough diffusion end; {$endif OSDARWIN} var // cascaded 128-bit random to avoid replay attacks - shared by all threads _EntropyGlobal: THash128Rec; procedure XorEntropy(var e: THash512Rec); var lec: PLecuyer; guid: THash128Rec; begin // note: we don't use RTL Random() here because it is not thread-safe if _EntropyGlobal.L = 0 then sysutils.CreateGuid(_EntropyGlobal.guid); // slow but rich initial value e.r[0].L := e.r[0].L xor _EntropyGlobal.L; e.r[0].H := e.r[0].H xor _EntropyGlobal.H; lec := @_Lecuyer; // lec^.rs#=0 at thread startup, but won't hurt e.r[1].c0 := e.r[1].c0 xor lec^.RawNext; // perfect forward security e.r[1].c1 := e.r[1].c1 xor lec^.RawNext; // but don't expose rs1,rs2,rs3 e.r[1].c2 := e.r[1].c2 xor lec^.RawNext; // any threadvar is thread-specific, so PtrUInt(lec) identifies this thread {$ifdef CPUINTELARM} e.r[1].c3 := e.r[1].c3 xor crc32c(PtrUInt(lec), @CpuFeatures, SizeOf(CpuFeatures)); {$else} e.r[1].c3 := e.r[1].c3 xor PtrUInt(lec); {$endif CPUINTELARM} // Windows CoCreateGuid, Linux /proc/sys/kernel/random/uuid, FreeBSD syscall, // then fallback to /dev/urandom or RTL mtwist_u32rand CreateGuid(guid.guid); // not from sysutils: redefined above for OSDARWIN e.r[2].L := e.r[2].L xor guid.L; e.r[2].H := e.r[2].H xor guid.H; // no mormot.core.os yet, so we can't use QueryPerformanceMicroSeconds() unaligned(PDouble(@e.r[3].Lo)^) := Now * 2123923447; // cross-platform time {$ifdef CPUINTEL} // use low-level Intel/AMD opcodes e.r[3].Lo := e.r[3].Lo xor Rdtsc; RdRand32(@e.r[0].c, length(e.r[0].c)); e.r[3].Hi := e.r[3].Hi xor Rdtsc; // has slightly changed in-between {$else} {$ifdef OSDARWIN} // fallback to known OS API on Mac M1/M2 e.r[3].Lo := e.r[3].Lo xor mach_absolute_time; // as defined above e.r[3].Hi := e.r[3].Hi xor mach_continuous_time; {$endif OSDARWIN} e.r[3].Hi := e.r[3].Hi xor GetTickCount64; // always defined in FPC RTL {$endif CPUINTEL} crc128c(@e, SizeOf(e), _EntropyGlobal.b); // simple diffusion to move forward end; procedure MoveSwap(dst, src: PByte; n: PtrInt); begin if n <= 0 then exit; inc(dst, n); repeat dec(dst); dst^ := src^; inc(src); dec(n); until n = 0; end; procedure TLecuyer.Seed(entropy: PByteArray; entropylen: PtrInt); var e: THash512Rec; h: THash128Rec; i, j: PtrInt; begin if entropy <> nil then for i := 0 to entropylen - 1 do begin j := i and (SizeOf(e) - 1); // insert into the 64 bytes of e.b[] e.b[j] := {%H-}e.b[j] xor entropy^[i]; end; repeat XorEntropy(e); // 512-bit from RdRand32 + Rdtsc + Now + CreateGuid DefaultHasher128(@h, @e, SizeOf(e)); // may be AesNiHash128 rs1 := rs1 xor h.c0; rs2 := rs2 xor h.c1; rs3 := rs3 xor h.c2; until (rs1 > 1) and (rs2 > 7) and (rs3 > 15); seedcount := h.c3 shr 24; // may seed slightly before 2^32 of output data for i := 1 to h.i3 and 7 do RawNext; // warm up end; procedure TLecuyer.SeedGenerator(fixedseed: QWord); begin SeedGenerator(@fixedseed, SizeOf(fixedseed)); end; procedure TLecuyer.SeedGenerator(fixedseed: pointer; fixedseedbytes: integer); begin rs1 := crc32c(0, fixedseed, fixedseedbytes); rs2 := crc32c(rs1, fixedseed, fixedseedbytes); rs3 := crc32c(rs2, fixedseed, fixedseedbytes); if rs1 < 2 then rs1 := 2; if rs2 < 8 then rs2 := 8; if rs3 < 16 then rs3 := 16; seedcount := 1; // will reseet after 16 GB, i.e. 2^32 of output data end; function TLecuyer.RawNext: cardinal; begin // not inlined for better code generation result := rs1; rs1 := ((result and -2) shl 12) xor (((result shl 13) xor result) shr 19); result := rs2; rs2 := ((result and -8) shl 4) xor (((result shl 2) xor result) shr 25); result := rs3; rs3 := ((result and -16) shl 17) xor (((result shl 3) xor result) shr 11); result := rs1 xor rs2 xor result; end; function TLecuyer.Next: cardinal; begin if seedcount = 0 then Seed // seed at startup, and after 2^32 of output data = 16 GB else inc(seedcount); result := RawNext; end; function TLecuyer.Next(max: cardinal): cardinal; begin result := (QWord(Next) * max) shr 32; end; function TLecuyer.NextQWord: QWord; begin PQWordRec(@result)^.L := Next; PQWordRec(@result)^.H := RawNext; // no need to check the Seed twice end; function TLecuyer.NextDouble: double; const COEFF32: double = 1.0 / (Int64(1) shl 32); begin result := Next * COEFF32; // 32-bit resolution is enough for our purpose end; procedure TLecuyer.Fill(dest: pointer; bytes: integer); var c: cardinal; begin if bytes <= 0 then exit; c := seedcount; inc(seedcount, cardinal(bytes) shr 2); if (c = 0) or // first use = seed at startup (c > seedcount) then // check for 32-bit overflow, i.e. after 16 GB Seed; repeat if bytes < 4 then break; PCardinal(dest)^ := PCardinal(dest)^ xor RawNext; // inlining won't change inc(PCardinal(dest)); dec(bytes, 4); if bytes = 0 then exit; until false; c := RawNext; repeat PByte(dest)^ := PByte(dest)^ xor c; inc(PByte(dest)); c := c shr 8; dec(bytes); until bytes = 0; end; procedure FillAnsiStringFromRandom(dest: PByteArray; size: PtrUInt); var len: PtrUInt; begin dec(size); len := dest[0]; // first random byte will make length if size = 31 then size := len and 31 // optimized for FillShort31() else if size = 255 then size := ToByte(len) else size := len mod size; dest[0] := size; if size <> 0 then repeat dest[size] := (cardinal(dest[size]) and 63) + 32; dec(size); until size = 0; end; procedure TLecuyer.FillShort(var dest: ShortString; size: PtrUInt); begin if size = 0 then begin dest[0] := #0; exit; end; if size > 255 then size := 256 else inc(size); Fill(@dest, size); FillAnsiStringFromRandom(@dest, size); end; procedure TLecuyer.FillShort31(var dest: TShort31); begin Fill(@dest, 32); FillAnsiStringFromRandom(@dest, 32); end; procedure Random32Seed(entropy: pointer; entropylen: PtrInt); begin _Lecuyer.Seed(entropy, entropylen); end; function Random32: cardinal; begin result := _Lecuyer.Next; end; function Random31: integer; begin result := _Lecuyer.Next shr 1; end; function Random32(max: cardinal): cardinal; begin result := (QWord(_Lecuyer.Next) * max) shr 32; end; function Random64: QWord; begin result := _Lecuyer.NextQWord; end; function RandomDouble: double; begin result := _Lecuyer.NextDouble; end; procedure RandomBytes(Dest: PByte; Count: integer); begin if Count > 0 then _Lecuyer.Fill(pointer(Dest), Count); end; procedure RandomShort31(var dest: TShort31); begin _Lecuyer.FillShort31(dest); end; procedure LecuyerEncrypt(key: Qword; var data: RawByteString); var gen: TLecuyer; begin if data = '' then exit; {$ifdef FPC} UniqueString(data); // @data[1] won't call UniqueString() under FPC :( {$endif FPC} gen.SeedGenerator(key); gen.Fill(@data[1], length(data)); FillZero(THash128(gen)); // to avoid forensic leak end; {$ifndef PUREMORMOT2} procedure FillRandom(Dest: PCardinal; CardinalCount: integer); begin if CardinalCount > 0 then _Lecuyer.Fill(pointer(Dest), CardinalCount shl 2); end; {$endif PUREMORMOT2} { MultiEvent* functions } function MultiEventFind(const EventList; const Event: TMethod): PtrInt; var Events: TMethodDynArray absolute EventList; begin if Event.Code <> nil then // callback assigned for result := 0 to length(Events) - 1 do if (Events[result].Code = Event.Code) and (Events[result].Data = Event.Data) then exit; result := -1; end; function MultiEventAdd(var EventList; const Event: TMethod): boolean; var Events: TMethodDynArray absolute EventList; n: PtrInt; begin result := false; n := MultiEventFind(EventList, Event); if n >= 0 then exit; // already registered result := true; n := length(Events); SetLength(Events, n + 1); Events[n] := Event; end; procedure MultiEventRemove(var EventList; const Event: TMethod); begin MultiEventRemove(EventList, MultiEventFind(EventList, Event)); end; procedure MultiEventRemove(var EventList; Index: integer); var Events: TMethodDynArray absolute EventList; max: integer; begin max := length(Events); if cardinal(Index) < cardinal(max) then begin dec(max); MoveFast(Events[Index + 1], Events[Index], (max - Index) * SizeOf(Events[Index])); SetLength(Events, max); end; end; procedure MultiEventMerge(var DestList; const ToBeAddedList); var Dest: TMethodDynArray absolute DestList; New: TMethodDynArray absolute ToBeAddedList; d, n: PtrInt; begin d := length(Dest); n := length(New); if n = 0 then exit; SetLength(Dest, d + n); MoveFast(New[0], Dest[d], n * SizeOf(TMethod)); end; function EventEquals(const eventA, eventB): boolean; var A: TMethod absolute eventA; B: TMethod absolute eventB; begin result := (A.Code = B.Code) and (A.Data = B.Data); end; type // 16KB/32KB hash table used by SynLZ - as used by the asm .inc files TOffsets = array[0..4095] of PAnsiChar; {$ifdef CPUINTEL} // optimized asm for x86 and x86_64 is located in include files {$ifndef HASNOSSE2} function IntegerScan(P: PCardinalArray; Count: PtrInt; Value: cardinal): PCardinal; begin Count := IntegerScanIndex(P, Count, Value); // SSE2 asm on Intel/AMD if Count >= 0 then result := @P[Count] else result := nil; end; function IntegerScanExists(P: PCardinalArray; Count: PtrInt; Value: cardinal): boolean; begin result := IntegerScanIndex(P, Count, Value) >= 0; // SSE2 asm on Intel/AMD end; {$endif HASNOSSE2} function HasHWAes: boolean; begin result := cfAESNI in CpuFeatures; end; procedure RdRand32(buffer: PCardinal; n: integer); begin if (n > 0) and (cfRAND in CpuFeatures) then repeat buffer^ := buffer^ xor RdRand32; inc(buffer); dec(n); until n = 0; end; type TIntelRegisters = record eax, ebx, ecx, edx: cardinal; end; {$ifdef CPUX64} {$include mormot.core.base.asmx64.inc} {$endif CPUX64} {$ifdef CPUX86} {$include mormot.core.base.asmx86.inc} {$endif CPUX86} procedure TestCpuFeatures; var regs: TIntelRegisters; c: cardinal; begin // retrieve CPUID raw flags FillChar(regs, SizeOf(regs), 0); // no FillCharFast here GetCpuid({eax=}1, {ecx=}0, regs); PIntegerArray(@CpuFeatures)^[0] := regs.edx; PIntegerArray(@CpuFeatures)^[1] := regs.ecx; GetCpuid(7, 0, regs); PIntegerArray(@CpuFeatures)^[2] := regs.ebx; PIntegerArray(@CpuFeatures)^[3] := regs.ecx; PIntegerArray(@CpuFeatures)^[4] := regs.edx; if regs.eax in [1..9] then // returned the maximum ecx value for eax=7 in eax begin GetCpuid(7, 1, regs); PIntegerArray(@CpuFeatures)^[5] := regs.eax; // just ignore regs.ebx PIntegerArray(@CpuFeatures)^[6] := regs.edx; if cfAVX10 in CpuFeatures then begin GetCpuid($24, 0, regs); CpuAvx10.MaxSubLeaf := regs.eax; CpuAvx10.Version := ToByte(regs.ebx); PByte(@CpuAvx10.Vector)^ := (regs.ebx shr 16) and 7; end; end; // validate accuracy of most used HW opcodes {$ifdef DISABLE_SSE42} // force fallback on Darwin x64 (as reported by alf) - clang asm bug? CpuFeatures := CpuFeatures - [cfSSE3, cfSSE42, cfPOPCNT, cfAESNI, cfCLMUL, cfAVX, cfAVX2, cfFMA]; {$else} if not (cfOSXS in CpuFeatures) or not IsXmmYmmOSEnabled then // AVX is available on the CPU, but not supported at OS context switch CpuFeatures := CpuFeatures - [cfAVX, cfAVX2, cfFMA]; {$endif DISABLE_SSE42} if cfRAND in CpuFeatures then try c := RdRand32; if RdRand32 = c then // most probably a RDRAND bug, e.g. on AMD Rizen 3000 exclude(CpuFeatures, cfRAND); except // may trigger an illegal instruction exception on some Ivy Bridge exclude(CpuFeatures, cfRAND); end; if cfSSE42 in CpuFeatures then try if crc32cby4sse42(0, 1) <> 3712330424 then exclude(CpuFeatures, cfSSE42); except // disable now on illegal instruction or incorrect result exclude(CpuFeatures, cfSSE42); end; if cfPOPCNT in CpuFeatures then try if GetBitsCountSse42(7) = 3 then GetBitsCountPtrInt := @GetBitsCountSse42; except // clearly invalid opcode exclude(CpuFeatures, cfPOPCNT); end; {$ifdef ASMX64} // note: cfERMS has no cpuid within some VMs -> ignore and assume present if cfAVX in CpuFeatures then begin include(X64CpuFeatures, cpuAVX); if cfAVX2 in CpuFeatures then include(X64CpuFeatures, cpuAVX2); if CpuFeatures * CPUAVX2HASWELL = CPUAVX2HASWELL then include(X64CpuFeatures, cpuHaswell); end; {$endif ASMX64} // redirect some CPU-aware functions {$ifdef ASMX86} {$ifndef HASNOSSE2} {$ifdef WITH_ERMS} if not (cfSSE2 in CpuFeatures) then begin ERMSB_MIN_SIZE_FWD := 0; // FillCharFast fallbacks to rep stosb on older CPU {$ifndef FPC_X86} ERMSB_MIN_SIZE_BWD := 0; // in both directions to bypass the SSE2 code {$endif FPC_X86} end // but MoveFast/SynLz are likely to abort -> recompile with HASNOSSE2 conditional // note: mormot.core.os.pas InitializeSpecificUnit will notify it on console else if cfERMS in CpuFeatures then ERMSB_MIN_SIZE_FWD := 4096; // "on 32-bit strings have to be at least 4KB" // backward rep movsd has no ERMS optimization so degrades performance {$endif WITH_ERMS} {$endif HASNOSSE2} if cfSSE2 in CpuFeatures then StrLen := @StrLenSSE2; {$endif ASMX86} if cfSSE42 in CpuFeatures then // for both i386 and x86_64 begin crc32c := @crc32csse42; crc32cby4 := @crc32cby4sse42; crcblock := @crcblocksse42; crcblocks := @crcblockssse42; DefaultHasher := @crc32csse42; InterningHasher := @crc32csse42; end; end; {$else not CPUINTEL} // fallback to pure pascal version for non-Intel CPUs function Hash32(Data: PCardinalArray; Len: integer): cardinal; var s1, s2: cardinal; i: integer; begin if Data <> nil then begin s1 := 0; s2 := 0; for i := 1 to Len shr 4 do begin // 16 bytes (128-bit) loop - aligned read inc(s1, Data[0]); inc(s2, s1); inc(s1, Data[1]); inc(s2, s1); inc(s1, Data[2]); inc(s2, s1); inc(s1, Data[3]); inc(s2, s1); Data := @Data[4]; end; for i := 1 to (Len shr 2) and 3 do begin // 4 bytes (DWORD) by loop inc(s1, Data[0]); inc(s2, s1); Data := @Data[1]; end; case Len and 3 of // remaining 0..3 bytes 1: inc(s1, PByte(Data)^); 2: inc(s1, PWord(Data)^); 3: inc(s1, PWord(Data)^ or (PByteArray(Data)^[2] shl 16)); end; inc(s2, s1); result := s1 xor (s2 shl 16); end else result := 0; end; const PRIME32_1 = 2654435761; PRIME32_2 = 2246822519; PRIME32_3 = 3266489917; PRIME32_4 = 668265263; PRIME32_5 = 374761393; {$ifdef FPC} // RolDWord is an intrinsic function under FPC :) function Rol13(value: cardinal): cardinal; inline; begin result := RolDWord(value, 13); end; {$else} function RolDWord(value: cardinal; count: integer): cardinal; inline; begin result := (value shl count) or (value shr (32 - count)); end; function Rol13(value: cardinal): cardinal; inline; begin result := (value shl 13) or (value shr 19); end; {$endif FPC} function xxHash32(crc: cardinal; P: PAnsiChar; len: cardinal): cardinal; var c1, c2, c3, c4: cardinal; PLimit, PEnd: PAnsiChar; begin PEnd := P + len; if len >= 16 then begin PLimit := PEnd - 16; c3 := crc; c2 := c3 + PRIME32_2; c1 := c2 + PRIME32_1; c4 := c3 - PRIME32_1; repeat c1 := PRIME32_1 * Rol13(c1 + PRIME32_2 * PCardinal(P)^); c2 := PRIME32_1 * Rol13(c2 + PRIME32_2 * PCardinal(P + 4)^); c3 := PRIME32_1 * Rol13(c3 + PRIME32_2 * PCardinal(P + 8)^); c4 := PRIME32_1 * Rol13(c4 + PRIME32_2 * PCardinal(P + 12)^); inc(P, 16); until not (P <= PLimit); result := RolDWord(c1, 1) + RolDWord(c2, 7) + RolDWord(c3, 12) + RolDWord(c4, 18); end else result := crc + PRIME32_5; inc(result, len); while P + 4 <= PEnd do begin inc(result, PCardinal(P)^ * PRIME32_3); result := RolDWord(result, 17) * PRIME32_4; inc(P, 4); end; while P < PEnd do begin inc(result, PByte(P)^ * PRIME32_5); result := RolDWord(result, 11) * PRIME32_1; inc(P); end; result := result xor (result shr 15); // inlined xxHash32Mixup() result := result * PRIME32_2; result := result xor (result shr 13); result := result * PRIME32_3; result := result xor (result shr 16); end; function SortDynArrayInteger(const A, B): integer; begin result := ord(integer(A) > integer(B)) - ord(integer(A) < integer(B)); end; function SortDynArrayCardinal(const A, B): integer; begin result := ord(cardinal(A) > cardinal(B)) - ord(cardinal(A) < cardinal(B)); end; function SortDynArrayInt64(const A, B): integer; begin result := ord(Int64(A) > Int64(B)) - ord(Int64(A) < Int64(B)); end; function SortDynArrayQWord(const A, B): integer; begin result := ord(QWord(A) > QWord(B)) - ord(QWord(A) < QWord(B)); end; function SortDynArrayPointer(const A, B): integer; begin result := ord(PtrUInt(A) > PtrUInt(B)) - ord(PtrUInt(A) < PtrUInt(B)); end; function SortDynArrayDouble(const A, B): integer; begin result := ord(double(A) > double(B)) - ord(double(A) < double(B)); end; function SortDynArraySingle(const A, B): integer; begin result := ord(single(A) > single(B)) - ord(single(A) < single(B)); end; function SortDynArrayAnsiString(const A, B): integer; begin result := StrComp(pointer(A), pointer(B)); end; function SortDynArrayRawByteString(const A, B): integer; var p1, p2: PByteArray; l1, l2: PtrInt; // FPC will use very efficiently the CPU registers begin // we can't use StrComp() since a RawByteString may contain #0 p1 := pointer(A); p2 := pointer(B); if p1 <> p2 then if p1 <> nil then if p2 <> nil then begin result := p1[0] - p2[0]; // compare first char for quicksort if result <> 0 then exit; l1 := PStrLen(PtrUInt(p1) - _STRLEN)^; l2 := PStrLen(PtrUInt(p2) - _STRLEN)^; result := l1; if l1 > l2 then l1 := l2; dec(result, l2); p1 := @p1[l1]; p2 := @p2[l1]; dec(l1); // we already compared the first char if l1 = 0 then exit; l1 := -l1; repeat if p1[l1] <> p2[l1] then break; inc(l1); if l1 = 0 then exit; until false; result := p1[l1] - p2[l1]; end else result := 1 // p2='' else result := -1 // p1='' else result := 0; // p1=p2 end; { FPC x86_64 Linux: 1000000 pas in 4.67ms i.e. 213,949,507/s, aver. 0us, 1.5 GB/s 1000000 asm in 4.14ms i.e. 241,196,333/s, aver. 0us, 1.8 GB/s 1000000 sse4.2 in 2.36ms i.e. 423,011,844/s, aver. 0us, 3.1 GB/s 1000000 FPC in 21.32ms i.e. 46,886,721/s, aver. 0us, 357.7 MB/s FPC i386 Windows: 1000000 pas in 3.40ms i.e. 293,944,738/s, aver. 0us, 1 GB/s 1000000 asm in 3.18ms i.e. 313,971,742/s, aver. 0us, 1.1 GB/s 1000000 sse4.2 in 2.74ms i.e. 364,166,059/s, aver. 0us, 1.3 GB/s 1000000 FPC in 8.18ms i.e. 122,204,570/s, aver. 0us, 466.1 MB/s notes: 1. AVX2 faster than popcnt on big buffers - https://arxiv.org/pdf/1611.07612.pdf 2. our pascal/asm versions below use the efficient Wilkes-Wheeler-Gill algorithm whereas FPC RTL's popcnt() is much slower } function GetBitsCountPas(value: PtrInt): PtrInt; begin // generic branchless Wilkes-Wheeler-Gill pure pascal version result := value; {$ifdef CPU64} result := result - ((result shr 1) and $5555555555555555); result := (result and $3333333333333333) + ((result shr 2) and $3333333333333333); result := (result + (result shr 4)) and $0f0f0f0f0f0f0f0f; inc(result, result shr 8); // avoid slow multiplication on ARM inc(result, result shr 16); inc(result, result shr 32); result := result and $7f; {$else} result := result - ((result shr 1) and $55555555); result := (result and $33333333) + ((result shr 2) and $33333333); result := (result + (result shr 4)) and $0f0f0f0f; inc(result, result shr 8); inc(result, result shr 16); result := result and $3f; {$endif CPU64} end; procedure mul64x64(constref left, right: QWord; out product: THash128Rec); var l: TQWordRec absolute left; r: TQWordRec absolute right; t1, t2: TQWordRec; begin // CPU-neutral implementation t1.V := QWord(l.L) * r.L; product.c0 := t1.L; t2.V := QWord(l.H) * r.L + t1.H; t1.V := QWord(l.L) * r.H + t2.L; product.H := QWord(l.H) * r.H + t2.H + t1.H; product.c1 := t1.V; end; function SynLZcompress1(src: PAnsiChar; size: integer; dst: PAnsiChar): integer; begin result := SynLZcompress1pas(src, size, dst); end; function SynLZdecompress1(src: PAnsiChar; size: integer; dst: PAnsiChar): integer; begin result := SynLZdecompress1pas(src, size, dst); end; function StrCntDecFree(var refcnt: TStrCnt): boolean; begin // fallback to RTL asm e.g. for ARM {$ifdef STRCNT32} result := InterLockedDecrement(refcnt) <= 0; {$else} result := InterLockedDecrement64(refcnt) <= 0; {$endif STRCNT32} end; // we don't check for ismultithread global function DACntDecFree(var refcnt: TDACnt): boolean; begin // fallback to RTL asm e.g. for ARM {$ifdef DACNT32} result := InterLockedDecrement(refcnt) <= 0; {$else} result := InterLockedDecrement64(refcnt) <= 0; {$endif DACNT32} end; procedure LockedInc32(int32: PInteger); begin InterlockedIncrement(int32^); end; procedure LockedDec32(int32: PInteger); begin InterlockedDecrement(int32^); end; procedure LockedInc64(int64: PInt64); begin {$ifdef FPC_64} InterlockedIncrement64(int64^); // we can use the existing 64-bit RTL function {$else} with PInt64Rec(int64)^ do if InterlockedIncrement(Lo) = 0 then InterlockedIncrement(Hi); // collission is highly unprobable {$endif FPC_64} end; function LockedExc(var Target: PtrUInt; NewValue, Comperand: PtrUInt): boolean; begin result := InterlockedCompareExchange( pointer(Target), pointer(NewValue), pointer(Comperand)) = pointer(Comperand); end; procedure LockedAdd(var Target: PtrUInt; Increment: PtrUInt); begin InterlockedExchangeAdd(pointer(Target), pointer(Increment)); end; procedure LockedAdd32(var Target: cardinal; Increment: cardinal); begin InterlockedExchangeAdd(Target, Increment); end; procedure LockedDec(var Target: PtrUInt; Decrement: PtrUInt); begin InterlockedExchangeAdd(pointer(Target), pointer(-PtrInt(Decrement))); end; procedure bswap64array(a,b: PQWordArray; n: PtrInt); var i: PtrInt; begin for i := 0 to n - 1 do b^[i] := {$ifdef FPC}SwapEndian{$else}bswap64{$endif}(a^[i]); end; function bswap32(a: cardinal): cardinal; begin result := SwapEndian(a); // use fast platform-specific function end; function bswap64(const a: QWord): QWord; begin result := SwapEndian(a); // use fast platform-specific function end; function ByteScanIndex(P: PByteArray; Count: PtrInt; Value: byte): PtrInt; begin result := IndexByte(P^, Count, Value); // use FPC RTL end; function WordScanIndex(P: PWordArray; Count: PtrInt; Value: word): PtrInt; begin result := IndexWord(P^, Count, Value); // use FPC RTL end; function IntegerScan(P: PCardinalArray; Count: PtrInt; Value: cardinal): PCardinal; begin result := nil; if P = nil then exit; Count := PtrUInt(@P[Count - 4]); // per-four loop is faster than FPC RTL repeat if PtrUInt(P) > PtrUInt(Count) then break; if P^[0] <> Value then if P^[1] <> Value then if P^[2] <> Value then if P^[3] <> Value then begin P := @P[4]; continue; end else result := @P[3] else result := @P[2] else result := @P[1] else result := pointer(P); exit; until false; inc(Count, 4 * SizeOf(Value)); result := pointer(P); repeat if PtrUInt(result) >= PtrUInt(Count) then break; if result^ = Value then exit; inc(result); until false; result := nil; end; function IntegerScanExists(P: PCardinalArray; Count: PtrInt; Value: cardinal): boolean; begin if P <> nil then begin result := true; Count := PtrInt(@P[Count - 4]); repeat if PtrUInt(P) > PtrUInt(Count) then break; if (P^[0] = Value) or (P^[1] = Value) or (P^[2] = Value) or (P^[3] = Value) then exit; P := @P[4]; until false; inc(Count, 4 * SizeOf(Value)); repeat if PtrUInt(P) >= PtrUInt(Count) then break; if P^[0] = Value then exit; P := @P[1]; until false; end; result := false; end; function IntegerScanIndex(P: PCardinalArray; Count: PtrInt; Value: cardinal): PtrInt; begin result := PtrUInt(IntegerScan(P, Count, Value)); if result = 0 then dec(result) else begin dec(result, PtrUInt(P)); result := result shr 2; end; end; {$ifdef CPUARM3264} // ARM-specific code {$ifdef OSLINUXANDROID} // read CpuFeatures from Linux envp const AT_HWCAP = 16; AT_HWCAP2 = 26; procedure TestCpuFeatures; var p: PPChar; caps: TArmHwCaps; begin // C library function getauxval() is not always available -> use system.envp caps := []; try p := system.envp; while p^ <> nil do inc(p); inc(p); // auxv is located after the last textual environment variable repeat if PtrUInt(p[0]) = AT_HWCAP then // 32-bit or 64-bit entries = PtrUInt PCardinalArray(@caps)[0] := PtrUInt(p[1]) else if PtrUInt(p[0]) = AT_HWCAP2 then PCardinalArray(@caps)[1] := PtrUInt(p[1]); p := @p[2]; until p[0] = nil; except // may happen on some untested Operating System caps := []; // is likely to be invalid end; CpuFeatures := caps; end; {$else} procedure TestCpuFeatures; begin // perhaps system.envp would work somewhat, but the HWCAP items don't match end; {$endif OSLINUXANDROID} function HasHWAes: boolean; begin result := ahcAES in CpuFeatures; end; {$else} // non Intel nor ARM CPUs procedure TestCpuFeatures; begin end; function HasHWAes: boolean; begin result := false; end; {$endif CPUARM3264} {$endif CPUINTEL} {$ifndef ASMINTEL} // fallback to pure pascal version for ARM or Intel PIC function crc32fasttab(crc: cardinal; buf: PAnsiChar; len: cardinal; tab: PCrc32tab): cardinal; inline; begin // on ARM, we use slicing-by-4 to avoid polluting smaller L1 cache result := not crc; if (buf <> nil) and (len > 0) then begin repeat if PtrUInt(buf) and 3 = 0 then // align to 4 bytes boundary break; result := tab[0, ToByte(result xor ord(buf^))] xor (result shr 8); dec(len); inc(buf); until len = 0; if len >= 4 then repeat result := result xor PCardinal(buf)^; inc(buf, 4); dec(len, 4); result := tab[3, ToByte(result)] xor tab[2, ToByte(result shr 8)] xor tab[1, ToByte(result shr 16)] xor tab[0, ToByte(result shr 24)]; until len < 4; while len > 0 do begin result := tab[0, ToByte(result xor ord(buf^))] xor (result shr 8); dec(len); inc(buf); end; end; result := not result; end; function StrInt32(P: PAnsiChar; val: PtrInt): PAnsiChar; begin if val < 0 then begin result := StrUInt32(P, PtrUInt(-val)) - 1; result^ := '-'; end else result := StrUInt32(P, val); end; function StrUInt32(P: PAnsiChar; val: PtrUInt): PAnsiChar; var c100: PtrUInt; // val/c100 are QWord on 64-bit CPU tab: PWordArray; begin // this code is faster than Borland's original str() or IntToStr() tab := @TwoDigitLookupW; repeat if val < 10 then begin dec(P); P^ := AnsiChar(val + ord('0')); break; end else if val < 100 then begin dec(P, 2); PWord(P)^ := tab[val]; break; end; dec(P, 2); c100 := val div 100; // FPC will use fast reciprocal dec(val, c100 * 100); PWord(P)^ := tab[val]; val := c100; if c100 = 0 then break; until false; result := P; end; {$endif ASMINTEL} { ************ Buffers (e.g. Hashing and SynLZ compression) Raw Functions } {$ifndef CPUX64} // there is fast branchless SSE2 assembly on x86-64 function BufferLineLength(Text, TextEnd: PUtf8Char): PtrInt; var c: byte; begin result := PtrUInt(Text) - 1; repeat inc(result); if PtrUInt(result) < PtrUInt(TextEnd) then begin c := PByte(result)^; if (c > 13) or ((c <> 10) and (c <> 13)) then continue; end; break; until false; dec(result, PtrInt(Text)); // returns length end; function PosChar(Str: PUtf8Char; Chr: AnsiChar): PUtf8Char; var c: AnsiChar; begin result := nil; if Str = nil then exit; repeat c := Str^; if c = #0 then exit else if c = Chr then break; inc(Str); until false; result := Str; end; function MemCmp(P1, P2: PByteArray; L: PtrInt): integer; begin // caller ensured that P1<>nil, P2<>nil and L>0 -> aggressively inlined asm result := 0; if L <= 0 then exit; inc(PtrUInt(P1), PtrUInt(L)); inc(PtrUInt(P2), PtrUInt(L)); L := -L; repeat if P1[L] <> P2[L] then break; inc(L); if L <> 0 then continue; exit; until false; result := P1[L] - P2[L]; end; {$endif CPUX64} function SynLZcompressdestlen(in_len: integer): integer; begin // get maximum possible (worse) compressed size for out_p result := in_len + in_len shr 3 + 16; end; function SynLZdecompressdestlen(in_p: PAnsiChar): integer; begin // get uncompressed size from lz-compressed buffer (to reserve memory, e.g.) result := PWord(in_p)^; if result and $8000 <> 0 then result := (result and $7fff) or (integer(PWord(in_p + 2)^) shl 15); end; function SynLZcompress1pas(src: PAnsiChar; size: integer; dst: PAnsiChar): integer; var dst_beg, // initial dst value src_end, // real last byte available in src src_endmatch, // last byte to try for hashing o: PAnsiChar; CWbit: byte; CWpoint: PCardinal; v, h, cached, t, tmax: PtrUInt; offset: TOffsets; cache: array[0..4095] of cardinal; // 16KB+16KB=32KB on stack (48KB for cpu64) begin dst_beg := dst; // 1. store in_len if size >= $8000 then begin // size in 32KB..2GB -> stored as integer PWord(dst)^ := $8000 or (size and $7fff); PWord(dst + 2)^ := size shr 15; inc(dst, 4); end else begin PWord(dst)^ := size; // size<32768 -> stored as word if size = 0 then begin result := 2; exit; end; inc(dst, 2); end; // 2. compress src_end := src + size; src_endmatch := src_end - (6 + 5); CWbit := 0; CWpoint := pointer(dst); PCardinal(dst)^ := 0; inc(dst, SizeOf(CWpoint^)); FillCharFast(offset, SizeOf(offset), 0); // fast 16KB reset to 0 // 1. main loop to search using hash[] if src <= src_endmatch then repeat v := PCardinal(src)^; h := ((v shr 12) xor v) and 4095; o := offset[h]; offset[h] := src; cached := v xor {%H-}cache[h]; // o=nil if cache[h] is uninitialized cache[h] := v; if (cached and $00ffffff = 0) and (o <> nil) and (src - o > 2) then begin CWpoint^ := CWpoint^ or (cardinal(1) shl CWbit); inc(src, 2); inc(o, 2); t := 1; tmax := src_end - src - 1; if tmax >= (255 + 16) then tmax := (255 + 16); while (o[t] = src[t]) and (t < tmax) do inc(t); inc(src, t); h := h shl 4; // here we have always t>0 if t <= 15 then begin // mark 2 to 17 bytes -> size=1..15 PWord(dst)^ := integer(t or h); inc(dst, 2); end else begin // mark 18 to (255+16) bytes -> size=0, next byte=t dec(t, 16); PWord(dst)^ := h; // size=0 dst[2] := ansichar(t); inc(dst, 3); end; end else begin dst^ := src^; inc(src); inc(dst); end; if CWbit < 31 then begin inc(CWbit); if src <= src_endmatch then continue else break; end else begin CWpoint := pointer(dst); PCardinal(dst)^ := 0; inc(dst, SizeOf(CWpoint^)); CWbit := 0; if src <= src_endmatch then continue else break; end; until false; // 2. store remaining bytes if src < src_end then repeat dst^ := src^; inc(src); inc(dst); if CWbit < 31 then begin inc(CWbit); if src < src_end then continue else break; end else begin PCardinal(dst)^ := 0; inc(dst, 4); CWbit := 0; if src < src_end then continue else break; end; until false; result := dst - dst_beg; end; // better code generation with sub-functions for raw decoding procedure SynLZdecompress1passub(src, src_end, dst: PAnsiChar; var offset: TOffsets); var last_hashed: PAnsiChar; // initial src and dst value {$ifdef CPU64} o: PAnsiChar; {$endif CPU64} CW, CWbit: cardinal; v, t, h: PtrUInt; label nextCW; begin last_hashed := dst - 1; nextCW: CW := PCardinal(src)^; inc(src, 4); CWbit := 1; if src < src_end then repeat if CW and CWbit = 0 then begin dst^ := src^; inc(src); inc(dst); if src >= src_end then break; if last_hashed < dst - 3 then begin inc(last_hashed); v := PCardinal(last_hashed)^; offset[((v shr 12) xor v) and 4095] := last_hashed; end; CWbit := CWbit shl 1; if CWbit <> 0 then continue else goto nextCW; end else begin h := PWord(src)^; inc(src, 2); t := (h and 15) + 2; if t = 2 then begin t := ord(src^) + (16 + 2); inc(src); end; h := h shr 4; {$ifdef CPU64} o := offset[h]; if PtrUInt(dst - o) < t then // overlap -> move byte-by-byte MoveByOne(o, dst, t) else if t <= 8 then PInt64(dst)^ := PInt64(o)^ // much faster in practice else MoveFast(o^, dst^, t); // safe since src_endmatch := src_end-(6+5) {$else} if PtrUInt(dst - offset[h]) < t then MoveByOne(offset[h], dst, t) else if t > 8 then MoveFast(offset[h]^, dst^, t) else PInt64(dst)^ := PInt64(offset[h])^; {$endif CPU64} if src >= src_end then break; if last_hashed < dst then repeat // decompressed bytes should update the hash table inc(last_hashed); v := PCardinal(last_hashed)^; offset[((v shr 12) xor v) and 4095] := last_hashed; until last_hashed >= dst; inc(dst, t); last_hashed := dst - 1; CWbit := CWbit shl 1; if CWbit <> 0 then continue else goto nextCW; end; until false; end; function SynLZdecompress1pas(src: PAnsiChar; size: integer; dst: PAnsiChar): integer; var offset: TOffsets; src_end: PAnsiChar; begin src_end := src + size; result := PWord(src)^; if result = 0 then exit; inc(src, 2); if result and $8000 <> 0 then begin result := (result and $7fff) or (integer(PWord(src)^) shl 15); inc(src, 2); end; SynLZdecompress1passub(src, src_end, dst, offset); end; procedure SynLZdecompress1partialsub(src, dst, src_end, dst_end: PAnsiChar; var offset: TOffsets); var last_hashed: PAnsiChar; // initial src and dst value CWbit, CW: integer; v, t, h: PtrUInt; {$ifdef CPU64} o: PAnsiChar; {$endif CPU64} label nextCW; begin last_hashed := dst - 1; nextCW: CW := PCardinal(src)^; inc(src, 4); CWbit := 1; if src < src_end then repeat if CW and CWbit = 0 then begin dst^ := src^; inc(src); inc(dst); if (src >= src_end) or (dst >= dst_end) then break; if last_hashed < dst - 3 then begin inc(last_hashed); v := PCardinal(last_hashed)^; offset[((v shr 12) xor v) and 4095] := last_hashed; end; CWbit := CWbit shl 1; if CWbit <> 0 then continue else goto nextCW; end else begin h := PWord(src)^; inc(src, 2); t := (h and 15) + 2; h := h shr 4; if t = 2 then begin t := ord(src^) + (16 + 2); inc(src); end; if dst + t >= dst_end then begin // avoid buffer overflow by all means MoveByOne(offset[h], dst, dst_end - dst); break; end; {$ifdef CPU64} o := offset[h]; if (t <= 8) or (PtrUInt(dst - o) < t) then MoveByOne(o, dst, t) else MoveFast(o^, dst^, t); {$else} if (t <= 8) or (PtrUInt(dst - offset[h]) < t) then MoveByOne(offset[h], dst, t) else MoveFast(offset[h]^, dst^, t); {$endif CPU64} if src >= src_end then break; if last_hashed < dst then repeat inc(last_hashed); v := PCardinal(last_hashed)^; offset[((v shr 12) xor v) and 4095] := last_hashed; until last_hashed >= dst; inc(dst, t); last_hashed := dst - 1; CWbit := CWbit shl 1; if CWbit <> 0 then continue else goto nextCW; end; until false; end; function SynLZdecompress1partial(src: PAnsiChar; size: integer; dst: PAnsiChar; maxDst: integer): integer; var offset: TOffsets; src_end: PAnsiChar; begin src_end := src + size; result := PWord(src)^; if result = 0 then exit; inc(src, 2); if result and $8000 <> 0 then begin result := (result and $7fff) or (integer(PWord(src)^) shl 15); inc(src, 2); end; if maxDst < result then result := maxDst; if result > 0 then SynLZdecompress1partialsub(src, dst, src_end, dst + result, offset); end; function CompressSynLZ(var Data: RawByteString; Compress: boolean): RawUtf8; var DataLen, len: integer; P: PAnsiChar; tmp: TSynTempBuffer; begin DataLen := length(Data); if DataLen <> 0 then // '' is compressed and uncompressed to '' if Compress then begin len := SynLZcompressdestlen(DataLen) + 8; P := tmp.Init(len); PCardinal(P)^ := Hash32(pointer(Data), DataLen); len := SynLZcompress1(pointer(Data), DataLen, P + 8); PCardinal(P + 4)^ := Hash32(pointer(P + 8), len); FastSetRawByteString(Data, P, len + 8); {%H-}tmp.Done; end else begin result := ''; P := pointer(Data); if (DataLen <= 8) or (Hash32(pointer(P + 8), DataLen - 8) <> PCardinal(P + 4)^) then exit; len := SynLZdecompressdestlen(P + 8); tmp.Init(len); if (len = 0) or ((SynLZDecompress1(P + 8, DataLen - 8, tmp.buf) = len) and (Hash32(tmp.buf, len) = PCardinal(P)^)) then FastSetRawByteString(Data, tmp.buf, len); {%H-}tmp.Done; end; result := 'synlz'; end; function CompressSynLZGetHash32(const Data: RawByteString): cardinal; var DataLen: integer; P: PAnsiChar; begin DataLen := length(Data); P := pointer(Data); if (DataLen <= 8) or (Hash32(pointer(P + 8), DataLen - 8) <> PCardinal(P + 4)^) then result := 0 else result := PCardinal(P)^; end; const RLE_CW = $5a; // any byte would do - this one is nothing special but for me function RleEncode(dst: PByteArray; v, n: PtrUInt): PByteArray; {$ifdef HASINLINE} inline; {$endif} begin if (n > 3) or (v = RLE_CW) then // encode as dst[0]=RLE_CW dst[1]=count dst[2]=value begin v := v shl 16; inc(v, RLE_CW); while n > 255 do begin PCardinal(dst)^ := v + 255 shl 8; dst := @dst[3]; dec(n, 255); end; inc(v, n shl 8); result := @dst[3]; end else begin inc(v, (v shl 8) + (v shl 16)); // append the value n (=1,2,3) times result := @dst[n]; // seems faster with branchless move end; PCardinal(dst)^ := v; end; function RleCompress(src, dst: PByteArray; srcsize, dstsize: PtrUInt): PtrInt; var dststart: PAnsiChar; c, b, n: PtrUInt; begin dststart := PAnsiChar(dst); if srcsize <> 0 then begin dstsize := PtrUInt(@dst[dstsize - 3]); // pointer(dstsize) = dstmax b := src[0]; n := 0; repeat c := src[0]; inc(PByte(src)); if c = b then begin inc(n); dec(srcsize); if (srcsize = 0) or (PtrUInt(dst) >= PtrUInt(dstsize)) then break; end else // dedicated if n = 1 then .. branch was slower begin dst := RleEncode(dst, b, n); n := 1; b := c; dec(srcsize); if (srcsize = 0) or (PtrUInt(dst) >= PtrUInt(dstsize)) then break; end; until false; dst := RleEncode(dst, b, n); if PtrUInt(dst) >= PtrUInt(dstsize) then begin result := -1; exit; end; end; result := PAnsiChar(dst) - dststart; end; {$ifdef CPUINTEL} {$ifndef HASNOSSE2} {$define INLINEDSEARCH} // leverage ByteScanIndex() SSE2 asm {$endif HASNOSSE2} {$endif CPUINTEL} {.$define INLINEDFILL} // actually slower function RleUnCompress(src, dst: PByteArray; size: PtrUInt): PtrUInt; var dststart: PAnsiChar; {$ifdef INLINEDFILL} c: PtrInt; {$endif INLINEDFILL} v: PtrUInt; begin dststart := PAnsiChar(dst); if size > 0 then repeat {$ifdef INLINEDSEARCH} if src[0] <> RLE_CW then begin v := ByteScanIndex(src, size, RLE_CW); if PtrInt(v) < 0 then v := size; MoveFast(src^, dst^, v); inc(PByte(src), v); inc(PByte(dst), v); dec(size, v); if size = 0 then break; end; {$else} v := src[0]; if v <> RLE_CW then begin dst[0] := v; inc(PByte(dst)); inc(PByte(src)); dec(size); if size = 0 then break; end else {$endif INLINEDSEARCH} begin // here src[0]=RLE_CW src[1]=count src[2]=value {$ifdef INLINEDFILL} c := src[1]; v := src[2]; inc(PByte(dst), c); c := -c; repeat dst[c] := v; inc(c); until c = 0; {$else} v := src[1]; FillCharFast(dst^, v, src[2]); inc(PByte(dst), v); {$endif INLINEDFILL} inc(PByte(src), 3); dec(size, 3); if PtrInt(size) <= 0 then break; end until false; result := PAnsiChar(dst) - dststart; end; function RleUnCompressPartial(src, dst: PByteArray; size, max: PtrUInt): PtrUInt; var dststart: PAnsiChar; v, m: PtrUInt; begin dststart := PAnsiChar(dst); inc(max, PtrUInt(dst)); while (size > 0) and (PtrUInt(dst) < max) do begin v := src[0]; if v = RLE_CW then begin v := src[1]; m := max - PtrUInt(dst); if v > m then v := m; // compile as cmov on FPC FillCharFast(dst^, v, src[2]); inc(PByte(dst), v); inc(PByte(src), 3); dec(size, 3); end else begin dst[0] := v; inc(PByte(dst)); inc(PByte(src)); dec(size); end; end; result := PAnsiChar(dst) - dststart; end; { TSynTempBuffer } procedure TSynTempBuffer.Init(Source: pointer; SourceLen: PtrInt); begin len := SourceLen; if SourceLen <= 0 then buf := nil else begin if SourceLen <= SizeOf(tmp) - 16 then // max internal tmp is 4080 bytes buf := @tmp else GetMem(buf, SourceLen + 16); // +16 for trailing #0 and for PInteger() parsing if Source <> nil then begin MoveFast(Source^, buf^, len); PPtrInt(PAnsiChar(buf) + len)^ := 0; // init last 4/8 bytes (for valgrid) end; end; end; function TSynTempBuffer.InitOnStack: pointer; begin buf := @tmp; len := SizeOf(tmp); result := @tmp; end; procedure TSynTempBuffer.Init(const Source: RawByteString); begin Init(pointer(Source), length(Source)); end; function TSynTempBuffer.Init(Source: PUtf8Char): PUtf8Char; begin Init(Source, StrLen(Source)); result := buf; end; function TSynTempBuffer.Init(SourceLen: PtrInt): pointer; begin len := SourceLen; if SourceLen <= 0 then buf := nil else begin if SourceLen <= SizeOf(tmp) - 16 then // max internal tmp is 4080 bytes buf := @tmp else GetMem(buf, SourceLen + 16); // +16 for trailing #0 and buffer overflow PPtrInt(PAnsiChar(buf) + SourceLen)^ := 0; // init last 4/8 bytes end; result := buf; end; function TSynTempBuffer.Init: integer; begin buf := @tmp; result := SizeOf(tmp) - 16; // set to maximum safe size, which is 4080 bytes len := result; end; function TSynTempBuffer.InitRandom(RandomLen: integer): pointer; begin Init(RandomLen); RandomBytes(buf, RandomLen); result := buf; end; function TSynTempBuffer.InitIncreasing(Count, Start: PtrInt): PIntegerArray; begin Init((Count - Start) * 4); FillIncreasing(buf, Start, Count); result := buf; end; function TSynTempBuffer.InitZero(ZeroLen: PtrInt): pointer; begin if ZeroLen = 0 then ZeroLen := SizeOf(tmp) - 16; Init(ZeroLen); FillCharFast(buf^, ZeroLen, 0); result := buf; end; function TSynTempBuffer.BufEnd: pointer; begin result := PAnsiChar(buf) + len; end; procedure TSynTempBuffer.Done; begin if (buf <> @tmp) and (buf <> nil) then FreeMem(buf); end; procedure TSynTempBuffer.Done(EndBuf: pointer; var Dest: RawUtf8); begin if EndBuf = nil then Dest := '' else FastSetString(Dest, buf, PAnsiChar(EndBuf) - PAnsiChar(buf)); if (buf <> @tmp) and (buf <> nil) then FreeMem(buf); end; procedure OrMemory(Dest, Source: PByteArray; size: PtrInt); begin while size >= SizeOf(PtrInt) do begin dec(size, SizeOf(PtrInt)); PPtrInt(Dest)^ := PPtrInt(Dest)^ or PPtrInt(Source)^; inc(PPtrInt(Dest)); inc(PPtrInt(Source)); end; while size > 0 do begin dec(size); Dest[size] := Dest[size] or Source[size]; end; end; procedure XorMemory(Dest, Source: PByteArray; size: PtrInt); begin while size >= SizeOf(PtrInt) do begin dec(size, SizeOf(PtrInt)); PPtrInt(Dest)^ := PPtrInt(Dest)^ xor PPtrInt(Source)^; inc(PPtrInt(Dest)); inc(PPtrInt(Source)); end; while size > 0 do begin dec(size); Dest[size] := Dest[size] xor Source[size]; end; end; procedure XorMemory(Dest, Source1, Source2: PByteArray; size: PtrInt); begin while size >= SizeOf(PtrInt) do begin dec(size, SizeOf(PtrInt)); PPtrInt(Dest)^ := PPtrInt(Source1)^ xor PPtrInt(Source2)^; inc(PPtrInt(Dest)); inc(PPtrInt(Source1)); inc(PPtrInt(Source2)); end; while size > 0 do begin dec(size); Dest[size] := Source1[size] xor Source2[size]; end; end; procedure AndMemory(Dest, Source: PByteArray; size: PtrInt); begin while size >= SizeOf(PtrInt) do begin dec(size, SizeOf(PtrInt)); PPtrInt(Dest)^ := PPtrInt(Dest)^ and PPtrInt(Source)^; inc(PPtrInt(Dest)); inc(PPtrInt(Source)); end; while size > 0 do begin dec(size); Dest[size] := Dest[size] and Source[size]; end; end; function IsZero(P: pointer; Length: integer): boolean; var n: integer; begin result := false; n := Length shr 4; if n <> 0 then repeat // 16 bytes (4 DWORD) by loop - aligned read {$ifdef CPU64} if (PInt64(P)^ <> 0) or (PInt64Array(P)^[1] <> 0) then {$else} if (PCardinal(P)^ <> 0) or (PCardinalArray(P)^[1] <> 0) or (PCardinalArray(P)^[2] <> 0) or (PCardinalArray(P)^[3] <> 0) then {$endif CPU64} exit else inc(PByte(P), 16); dec(n); until n = 0; n := (Length shr 2) and 3; if n <> 0 then repeat // 4 bytes (1 DWORD) by loop if PCardinal(P)^ <> 0 then exit else inc(PByte(P), 4); dec(n); until n = 0; n := Length and 3; if n <> 0 then repeat // remaining content if PByte(P)^ <> 0 then exit else inc(PByte(P)); dec(n); until n = 0; result := true; end; function IsZeroSmall(P: pointer; Length: PtrInt): boolean; begin result := false; inc(PtrUInt(P), PtrUInt(Length)); Length := -Length; repeat if PByteArray(P)[Length] <> 0 then exit; inc(Length); until Length = 0; result := true; end; function crc32cfast(crc: cardinal; buf: PAnsiChar; len: cardinal): cardinal; begin result := crc32fasttab(crc, buf, len, @crc32ctab); end; function crc32fast(crc: cardinal; buf: PAnsiChar; len: cardinal): cardinal; begin result := crc32fasttab(crc, buf, len, @crc32tab); end; function crc32cBy4fast(crc, value: cardinal): cardinal; var tab: PCrc32tab; begin tab := @crc32ctab; result := crc xor value; result := tab[3, ToByte(result)] xor tab[2, ToByte(result shr 8)] xor tab[1, ToByte(result shr 16)] xor tab[0, ToByte(result shr 24)]; end; {$ifdef HASINLINE} function crc32cinlined(crc: cardinal; buf: PAnsiChar; len: cardinal): cardinal; var tab: PCrc32tab; begin result := not crc; tab := @crc32ctab; if len > 0 then repeat result := tab[0, ToByte(result xor ord(buf^))] xor (result shr 8); inc(buf); dec(len); until len = 0; result := not result; end; {$else} function crc32cinlined(crc: cardinal; buf: PAnsiChar; len: cardinal): cardinal; begin result := crc32c(crc, buf, len); end; {$endif HASINLINE} function crc64c(buf: PAnsiChar; len: cardinal): Int64; var lo: PtrInt; begin lo := crc32c(0, buf, len); result := Int64(lo) or (Int64(crc32c(lo, buf, len)) shl 32); end; function crc32cTwice(seed: QWord; buf: PAnsiChar; len: cardinal): QWord; begin PQWordRec(@result)^.L := crc32c(PQWordRec(@seed)^.L, buf, len); PQWordRec(@result)^.H := crc32c(PQWordRec(@seed)^.H, buf, len); end; function crc63c(buf: PAnsiChar; len: cardinal): Int64; var lo: PtrInt; begin lo := crc32c(0, buf, len); result := Int64(lo) or (Int64(crc32c(lo, buf, len) and $7fffffff) shl 32); end; procedure crc128c(buf: PAnsiChar; len: cardinal; out crc: THash128); var h: THash128Rec absolute crc; h1, h2: cardinal; begin // see https://goo.gl/Pls5wi h1 := crc32c(0, buf, len); h2 := crc32c(h1, buf, len); h.i0 := h1; inc(h1, h2); h.i1 := h1; inc(h1, h2); h.i2 := h1; inc(h1, h2); h.i3 := h1; end; procedure crc256c(buf: PAnsiChar; len: cardinal; out crc: THash256); var h: THash256Rec absolute crc; h1, h2: cardinal; begin // see https://goo.gl/Pls5wi h1 := crc32c(0, buf, len); h2 := crc32c(h1, buf, len); h.i0 := h1; inc(h1, h2); h.i1 := h1; inc(h1, h2); h.i2 := h1; inc(h1, h2); h.i3 := h1; inc(h1, h2); h.i4 := h1; inc(h1, h2); h.i5 := h1; inc(h1, h2); h.i6 := h1; inc(h1, h2); h.i7 := h1; end; procedure crc32c128(hash: PHash128; buf: PAnsiChar; len: cardinal); var blocks: cardinal; begin blocks := len shr 4; if blocks <> 0 then begin crcblocks(pointer(hash), pointer(buf), blocks); blocks := blocks shl 4; inc(buf, blocks); dec(len, blocks); end; if len <> 0 then with PHash128Rec(hash)^ do begin c0 := crc32c(c0, buf, len); c1 := crc32c(c1, buf, len); c2 := crc32c(c2, buf, len); c3 := crc32c(c3, buf, len); end; end; function crc16(Data: PAnsiChar; Len: integer): cardinal; var i, j: integer; begin result := $ffff; for i := 0 to Len - 1 do begin result := result xor (ord(Data[i]) shl 8); for j := 1 to 8 do if result and $8000 <> 0 then result := (result shl 1) xor $1021 else result := result shl 1; end; result := result and $ffff; end; function Hash32(const Text: RawByteString): cardinal; begin result := Hash32(pointer(Text), Length(Text)); end; function DefaultHash(const s: RawByteString): cardinal; begin result := DefaultHasher(0, pointer(s), length(s)); end; function DefaultHash(const b: TBytes): cardinal; begin result := DefaultHasher(0, pointer(b), length(b)); end; function crc32cHash(const s: RawByteString): cardinal; begin result := crc32c(0, pointer(s), length(s)); end; function crc32cHash(const b: TBytes): cardinal; begin result := crc32c(0, pointer(b), length(b)); end; function Hash128To64(const b: THash128): QWord; begin result := THash128Rec(b).L xor (THash128Rec(b).H * QWord(2685821657736338717)); end; function xxHash32Mixup(crc: cardinal): cardinal; begin result := crc; result := result xor (result shr 15); result := result * 2246822519; result := result xor (result shr 13); result := result * 3266489917; result := result xor (result shr 16); end; procedure crcblockone(crc128, data128: PBlock128; tab: PCrc32tab); {$ifdef HASINLINE} inline; {$endif} var c: cardinal; begin c := crc128^[0] xor data128^[0]; crc128^[0] := tab[3, ToByte(c)] xor tab[2, ToByte(c shr 8)] xor tab[1, ToByte(c shr 16)] xor tab[0, ToByte(c shr 24)]; c := crc128^[1] xor data128^[1]; crc128^[1] := tab[3, ToByte(c)] xor tab[2, ToByte(c shr 8)] xor tab[1, ToByte(c shr 16)] xor tab[0, ToByte(c shr 24)]; c := crc128^[2] xor data128^[2]; crc128^[2] := tab[3, ToByte(c)] xor tab[2, ToByte(c shr 8)] xor tab[1, ToByte(c shr 16)] xor tab[0, ToByte(c shr 24)]; c := crc128^[3] xor data128^[3]; crc128^[3] := tab[3, ToByte(c)] xor tab[2, ToByte(c shr 8)] xor tab[1, ToByte(c shr 16)] xor tab[0, ToByte(c shr 24)]; end; {$ifndef ASMX86} // those functions have their tuned x86 asm version {$ifdef CPUX64} function CompareMem(P1, P2: Pointer; Length: PtrInt): boolean; begin result := MemCmp(P1, P2, Length) = 0; // use our SSE2 optimized asm end; {$else} function CompareMem(P1, P2: Pointer; Length: PtrInt): boolean; label zero; begin // this awfull code compiles well under FPC and Delphi on 32-bit and 64-bit Length := PtrInt(@PAnsiChar(P1)[Length - SizeOf(PtrInt) * 2]); // = 2*PtrInt end if Length >= PtrInt(PtrUInt(P1)) then begin if PPtrInt(PtrUInt(P1))^ <> PPtrInt(P2)^ then // compare first PtrInt bytes goto zero; inc(PPtrInt(P1)); inc(PPtrInt(P2)); dec(PtrInt(P2), PtrInt(PtrUInt(P1))); PtrInt(PtrUInt(P1)) := PtrInt(PtrUInt(P1)) and - SizeOf(PtrInt); // align inc(PtrInt(P2), PtrInt(PtrUInt(P1))); if Length >= PtrInt(PtrUInt(P1)) then repeat // compare 4 aligned PtrInt per loop if (PPtrInt(PtrUInt(P1))^ <> PPtrInt(P2)^) or (PPtrIntArray(P1)[1] <> PPtrIntArray(P2)[1]) then goto zero; inc(PByte(P1), SizeOf(PtrInt) * 2); inc(PByte(P2), SizeOf(PtrInt) * 2); if Length < PtrInt(PtrUInt(P1)) then break; if (PPtrInt(PtrUInt(P1))^ <> PPtrInt(P2)^) or (PPtrIntArray(P1)[1] <> PPtrIntArray(P2)[1]) then goto zero; inc(PByte(P1), SizeOf(PtrInt) * 2); inc(PByte(P2), SizeOf(PtrInt) * 2); until Length < PtrInt(PtrUInt(P1)); end; dec(Length, PtrInt(PtrUInt(P1)) - SizeOf(PtrInt) * 2); // back to real length if Length >= SizeOf(PtrInt) then begin if PPtrInt(PtrUInt(P1))^ <> PPtrInt(P2)^ then goto zero; inc(PPtrInt(P1)); inc(PPtrInt(P2)); dec(Length, SizeOf(PtrInt)); end; {$ifdef CPU64} if Length >= 4 then begin if PCardinal(P1)^ <> PCardinal(P2)^ then goto zero; inc(PCardinal(P1)); inc(PCardinal(P2)); dec(Length, 4); end; {$endif CPU64} if Length >= 2 then begin if PWord(P1)^ <> PWord(P2)^ then goto zero; inc(PWord(P1)); inc(PWord(P2)); dec(Length, 2); end; if Length >= 1 then if PByte(P1)^ <> PByte(P2)^ then goto zero; result := true; exit; zero: result := false; end; {$endif CPUX64} procedure crcblockfast(crc128, data128: PBlock128); begin crcblockone(crc128, data128, @crc32ctab); end; function fnv32(crc: cardinal; buf: PAnsiChar; len: PtrInt): cardinal; var i: PtrInt; begin if buf <> nil then for i := 0 to len - 1 do crc := (crc xor ord(buf[i])) * 16777619; result := crc; end; function kr32(crc: cardinal; buf: PAnsiChar; len: PtrInt): cardinal; var i: PtrInt; begin if buf <> nil then for i := 0 to len - 1 do begin crc := crc * 31; inc(crc, ord(buf[i])); end; result := crc; end; procedure YearToPChar(Y: PtrUInt; P: PUtf8Char); var d100: PtrUInt; tab: PWordArray; begin tab := @TwoDigitLookupW; d100 := Y div 100; // FPC will use fast reciprocal PWordArray(P)[0] := tab[d100]; PWordArray(P)[1] := tab[Y - (d100 * 100)]; end; {$endif ASMX86} function CompareBuf(const P1: RawByteString; P2: Pointer; P2Len: PtrInt): integer; begin result := ComparePtrInt(length(P1), P2Len); if result = 0 then result := MemCmp(pointer(P1), P2, P2Len); end; function CompareBuf(const P1, P2: RawByteString): integer; begin result := SortDynArrayRawByteString(P1, P2); end; function EqualBuf(const P1, P2: RawByteString): boolean; begin result := SortDynArrayRawByteString(P1, P2) = 0; end; procedure crcblocksfast(crc128, data128: PBlock128; count: integer); var tab: PCrc32tab; // good enough or PIC or ARM begin if count <= 0 then exit; tab := @crc32ctab; repeat crcblockone(crc128, data128, tab); // properly inlined inc(data128); dec(count); until count = 0; end; function SameValue(const A, B: Double; DoublePrec: double): boolean; var AbsA, AbsB, Res: double; begin if PInt64(@DoublePrec)^ = 0 then begin // Max(Min(Abs(A),Abs(B))*1E-12,1E-12) AbsA := Abs(A); AbsB := Abs(B); Res := 1E-12; if AbsA < AbsB then DoublePrec := AbsA * Res else DoublePrec := AbsB * Res; if DoublePrec < Res then DoublePrec := Res; end; if A < B then result := (B - A) <= DoublePrec else result := (A - B) <= DoublePrec; end; function SameValueFloat(const A, B: TSynExtended; DoublePrec: TSynExtended): boolean; var AbsA, AbsB, Res: TSynExtended; begin if DoublePrec = 0 then begin // Max(Min(Abs(A),Abs(B))*1E-12,1E-12) AbsA := Abs(A); AbsB := Abs(B); Res := 1E-12; // also for TSynExtended (FPC uses 1E-4!) if AbsA < AbsB then DoublePrec := AbsA * Res else DoublePrec := AbsB * Res; if DoublePrec < Res then DoublePrec := Res; end; if A < B then result := (B - A) <= DoublePrec else result := (A - B) <= DoublePrec; end; function CompareFloat(const A, B: double): integer; begin result := ord(A > B) - ord(A < B); end; procedure KahanSum(const Data: double; var Sum, Carry: double); var y, t: double; begin y := Data - Carry; t := Sum + y; Carry := (t - Sum) - y; Sum := t; end; { ************ Efficient Variant Values Conversion } procedure SetVariantNull(var Value: variant); begin VarClearAndSetType(Value, varNull); end; procedure ClearVariantForString(var Value: variant); var v: cardinal; begin v := TVarData(Value).VType; if v = varString then FastAssignNew(TVarData(Value).VAny) else begin VarClearAndSetType(Value, varString); TVarData(Value).VAny := nil; // to avoid GPF when assigning the value end; end; procedure RawByteStringToVariant(Data: PByte; DataLen: integer; var Value: variant); begin ClearVariantForString(Value); if (Data = nil) or (DataLen <= 0) then PCardinal(@Value)^ := varNull else FastSetRawByteString(RawByteString(TVarData(Value).VAny), Data, DataLen); end; procedure RawByteStringToVariant(const Data: RawByteString; var Value: variant); begin ClearVariantForString(Value); if Data = '' then PCardinal(@Value)^ := varNull else RawByteString(TVarData(Value).VAny) := Data; end; procedure VariantToUtf8(const Value: variant; var Dest: RawByteString); begin // sub-proc to avoid hidden temp variable in VariantToRawByteString Dest := {$ifdef UNICODE}RawByteString{$else}string{$endif}(Value); end; procedure VariantToRawByteString(const Value: variant; var Dest: RawByteString); begin case integer(TVarData(Value).VType) of varEmpty, varNull: Dest := ''; varString: Dest := RawByteString(TVarData(Value).VAny); varStringByRef: Dest := PRawByteString(TVarData(Value).VAny)^; varVariantByRef: VariantToRawByteString(PVariant(TVarData(Value).VPointer)^, Dest); else // not from RawByteStringToVariant() -> conversion to string VariantToUtf8(Value, Dest); end; end; function VarDataFromVariant(const Value: variant): PVarData; begin result := @Value; repeat if integer(result^.VType) <> varVariantByRef then exit; if result^.VPointer <> nil then result := result^.VPointer else begin result := @result^.VPointer; // so VType will point to 0=varEmpty exit; end; until false; end; function VarDataIsEmptyOrNull(VarData: pointer): boolean; begin with VarDataFromVariant(PVariant(VarData)^)^ do result := (cardinal(VType) <= varNull) or (cardinal(VType) = varNull or varByRef); end; function VarIsEmptyOrNull(const V: Variant): boolean; begin with VarDataFromVariant(V)^ do result := (cardinal(VType) <= varNull) or (cardinal(VType) = varNull or varByRef); end; function SetVariantUnRefSimpleValue(const Source: variant; var Dest: TVarData): boolean; var typ: cardinal; begin result := false; typ := TVarData(Source).VType; if typ and varByRef = 0 then exit; typ := typ and not varByRef; case typ of varVariant: if integer(PVarData(TVarData(Source).VPointer)^.VType) in VTYPE_SIMPLE then begin Dest := PVarData(TVarData(Source).VPointer)^; result := true; end; varEmpty..varDate, varBoolean, varShortInt..varWord64: begin PCardinal(@Dest)^ := typ; Dest.VInt64 := PInt64(TVarData(Source).VAny)^; result := true; end; end; end; function SetVarDataUnRefSimpleValue(V: PVarData; var tmp: TVarData): PVarData; {$ifdef HASINLINE}inline;{$endif} var typ: cardinal; begin typ := V^.VType; if typ and varByRef <> 0 then begin typ := typ and not varByRef; if typ in VTYPE_SIMPLE then begin PCardinal(@tmp)^ := typ; tmp.VInt64 := PInt64(V^.VAny)^; result := @tmp; exit; end end; result := nil; end; function VariantToInteger(const V: Variant; var Value: integer): boolean; var vd: PVarData; tmp: TVarData; begin result := false; vd := VarDataFromVariant(V); repeat case cardinal(vd^.VType) of varNull, varEmpty: Value := 0; varBoolean: if vd^.VBoolean then Value := 1 else Value := 0; // normalize varSmallint: Value := vd^.VSmallInt; varShortInt: Value := vd^.VShortInt; varWord: Value := vd^.VWord; varLongWord, varOleUInt: if vd^.VLongWord <= cardinal(High(integer)) then Value := vd^.VLongWord else exit; varByte: Value := vd^.VByte; varInteger, varOleInt: Value := vd^.VInteger; varWord64: if (vd^.VInt64 >= 0) and (vd^.VInt64 <= High(integer)) then Value := vd^.VInt64 else exit; varInt64: if (vd^.VInt64 >= Low(integer)) and (vd^.VInt64 <= High(integer)) then Value := vd^.VInt64 else exit; varDouble, varDate, varSingle, varCurrency, varString, varOleStr: exit; else begin vd := SetVarDataUnRefSimpleValue(vd, tmp{%H-}); if vd <> nil then continue; // avoid a goto exit; end; end; break; until false; result := true; end; function VariantToDouble(const V: Variant; var Value: double): boolean; var vd: PVarData; i64: Int64; begin vd := VarDataFromVariant(V); result := true; case cardinal(vd^.VType) of varEmpty, varNull: Value := 0; varDouble, varDate: Value := vd^.VDouble; varSingle: Value := vd^.VSingle; varCurrency: CurrencyToDouble(@vd^.VCurrency, Value); varDouble or varByRef, varDate or varByRef: Value := unaligned(PDouble(vd^.VAny)^); varSingle or varByRef: Value := {$ifdef FPC_REQUIRES_PROPER_ALIGNMENT}unaligned{$endif}( PSingle(vd^.VAny)^); varCurrency or varByRef: CurrencyToDouble(vd^.VAny, Value); else if VariantToInt64(PVariant(vd)^, i64) then Value := i64 else result := false; end; end; function VariantToDoubleDef(const V: Variant; const default: double = 0): double; begin if not VariantToDouble(V, result) then result := default; end; function VariantToCurrency(const V: Variant; var Value: currency): boolean; var vd: PVarData; tmp: TVarData; begin vd := VarDataFromVariant(V); result := true; case cardinal(vd^.VType) of varDouble, varDate: DoubleToCurrency(vd^.VDouble, Value); varSingle: DoubleToCurrency(vd^.VSingle, Value); varCurrency: Value := PCurrency(@vd^.VCurrency)^; varDouble or varByRef, varDate or varByRef: DoubleToCurrency(PDouble(vd^.VAny)^, Value); varSingle or varByRef: DoubleToCurrency(PSingle(vd^.VAny)^, Value); varCurrency or varByRef: Value := PCurrency(vd^.VAny)^; else if VariantToInt64(PVariant(vd)^, tmp.VInt64) then Int64ToCurrency(tmp.VInt64, Value) // also handle varEmpty,varNull else result := false; end; end; function VariantToBoolean(const V: Variant; var Value: boolean): boolean; var vd: PVarData; tmp: TVarData; begin vd := VarDataFromVariant(V); repeat case cardinal(vd^.VType) of varEmpty, varNull: begin result := false; exit; end; varBoolean: // 16-bit WordBool to 8-bit boolean if vd^.VBoolean then Value := true // normalize else Value := false; varInteger: // coming e.g. from TGetJsonField Value := vd^.VInteger = 1; varString: Value := GetBoolean(vd^.VAny); varOleStr: Value := WideString(vd^.VAny) = 'true'; {$ifdef HASVARUSTRING} varUString: Value := UnicodeString(vd^.VAny) = 'true'; {$endif HASVARUSTRING} else begin vd := SetVarDataUnRefSimpleValue(vd, tmp{%H-}); if vd <> nil then continue; result := false; exit; end; end; break; until false; result := true; end; function VariantToInt64(const V: Variant; var Value: Int64): boolean; var vd: PVarData; tmp: TVarData; begin vd := VarDataFromVariant(V); repeat case cardinal(vd^.VType) of varNull, varEmpty: Value := 0; varBoolean: if vd^.VBoolean then Value := 1 else Value := 0; // normalize varSmallint: Value := vd^.VSmallInt; varShortInt: Value := vd^.VShortInt; varWord: Value := vd^.VWord; varLongWord, varOleUInt: Value := vd^.VLongWord; varByte: Value := vd^.VByte; varInteger, varOleInt: Value := vd^.VInteger; varWord64: if vd^.VInt64 >= 0 then Value := vd^.VInt64 else begin result := false; exit; end; varInt64: Value := vd^.VInt64; else begin vd := SetVarDataUnRefSimpleValue(vd, tmp{%H-}); if vd <> nil then continue; result := false; exit; end; end; break; until false; result := true; end; function VariantToInt64Def(const V: Variant; DefaultValue: Int64): Int64; begin if not VariantToInt64(V, result) then result := DefaultValue; end; function VariantToIntegerDef(const V: Variant; DefaultValue: integer): integer; begin if not VariantToInteger(V, result) then result := DefaultValue; end; procedure RawUtf8ToVariant(Txt: PUtf8Char; TxtLen: integer; var Value: variant); begin ClearVariantForString(Value); FastSetString(RawUtf8(TVarData(Value).VString), Txt, TxtLen); end; procedure RawUtf8ToVariant(const Txt: RawUtf8; var Value: variant); begin ClearVariantForString(Value); if Txt = '' then exit; RawUtf8(TVarData(Value).VAny) := Txt; EnsureRawUtf8(RawByteString(TVarData(Value).VAny)); end; function RawUtf8ToVariant(const Txt: RawUtf8): variant; begin RawUtf8ToVariant(Txt, result{%H-}); end; procedure VariantStringToUtf8(const V: Variant; var result: RawUtf8); begin with VarDataFromVariant(V)^ do if cardinal(VType) = varString then result := RawUtf8(VString) else result := ''; end; function VariantStringToUtf8(const V: Variant): RawUtf8; begin VariantStringToUtf8(V, result{%H-}); end; procedure _VariantClearSeveral(V: PVariant; n: integer); begin if n > 0 then repeat VarClear(V^); inc(V); dec(n); until n = 0; end; function VariantCompSimple(const A, B: variant): integer; var a64, b64: Int64; af64, bf64: double; begin // directly handle ordinal and floating point values if VariantToInt64(A, a64) and VariantToInt64(B, b64) then result := CompareInt64(a64, b64) else if VariantToDouble(A, af64) and VariantToDouble(B, bf64) then result := CompareFloat(af64, bf64) else // inlined VarCompareValue() for complex/mixed types if A = B then result := 0 else if A < B then // both FPC and Delphi RTL require these two comparisons result := -1 else result := 1; end; function _SortDynArrayVariantComp(const A, B: TVarData; {%H-}caseInsensitive: boolean): integer; // caseInsensitive not supported by the RTL -> include mormot.core.variants begin result := VariantCompSimple(PVariant(@A)^, PVariant(@B)^); end; { ************ Sorting/Comparison Functions } function SortMatch(CompareResult: integer; CompareOperator: TCompareOperator): boolean; begin case CompareOperator of coEqualTo: result := CompareResult = 0; coNotEqualTo: result := CompareResult <> 0; coLessThan: result := CompareResult < 0; coLessThanOrEqualTo: result := CompareResult <= 0; coGreaterThan: result := CompareResult > 0; // coGreaterThanOrEqualTo: else result := CompareResult >= 0; end; end; function SortDynArrayVariant(const A, B): integer; begin result := SortDynArrayVariantComp(TVarData(A), TVarData(B), {caseins=}false); end; function SortDynArrayVariantI(const A, B): integer; begin result := SortDynArrayVariantComp(TVarData(A), TVarData(B), {caseins=}true); end; function SortDynArrayBoolean(const A, B): integer; begin if boolean(A) then // normalize (seldom used, anyway) if boolean(B) then result := 0 else result := 1 else if boolean(B) then result := -1 else result := 0; end; function SortDynArrayByte(const A, B): integer; begin result := byte(A) - byte(B); end; function SortDynArraySmallint(const A, B): integer; begin result := smallint(A) - smallint(B); end; function SortDynArrayShortint(const A, B): integer; begin result := shortint(A) - shortint(B); end; function SortDynArrayWord(const A, B): integer; begin result := word(A) - word(B); end; function SortDynArrayExtended(const A, B): integer; begin result := ord(TSynExtended(A) > TSynExtended(B)) - ord(TSynExtended(A) < TSynExtended(B)); end; function SortDynArrayString(const A, B): integer; begin {$ifdef UNICODE} result := StrCompW(PWideChar(A), PWideChar(B)); {$else} {$ifdef CPUINTEL} result := SortDynArrayAnsiString(A, B); // has its own optimized asm {$else} result := StrComp(PUtf8Char(A), PUtf8Char(B)); {$endif CPUINTEL} {$endif UNICODE} end; function SortDynArrayUnicodeString(const A, B): integer; begin // works for both tkWString and tkUString result := StrCompW(PWideChar(A), PWideChar(B)); end; function CompareHash(A, B: PPointer; Len: integer): integer; {$ifdef HASINLINE}inline;{$endif} begin repeat result := ComparePointer(A^, B^); // on FPC inlined is better than explicit if result <> 0 then exit; // trailing register-size memory is seldom equal during sort inc(A); inc(B); dec(Len); until Len = 0; end; function SortDynArray128(const A, B): integer; begin {$ifdef CPU64} result := ord(THash128Rec(A).L > THash128Rec(B).L) - ord(THash128Rec(A).L < THash128Rec(B).L); if result = 0 then result := ord(THash128Rec(A).H > THash128Rec(B).H) - ord(THash128Rec(A).H < THash128Rec(B).H); {$else} result := CompareHash(@A, @B, SizeOf(THash128) div SizeOf(pointer)); {$endif CPU64} end; function SortDynArray256(const A, B): integer; begin result := CompareHash(@A, @B, SizeOf(THash256) div SizeOf(pointer)); end; function SortDynArray512(const A, B): integer; begin result := CompareHash(@A, @B, SizeOf(THash512) div SizeOf(pointer)); end; function SortDynArrayPUtf8Char(const A, B): integer; begin result := StrComp(pointer(A), pointer(B)); end; function SortDynArrayShortString(const A, B): integer; var sa: shortstring absolute A; sb: shortstring absolute B; la, lb: PtrInt; begin la := ord(sa[0]); lb := ord(sb[0]); if la < lb then la := lb; result := MemCmp(@sa[1], @sb[1], la); if result = 0 then result := ord(sa[0]) - ord(sb[0]); end; {$if not defined(CPUX64ASM) and not defined(CPUX86)} // fallback if no asm procedure DynArrayHashTableAdjust(P: PIntegerArray; deleted: integer; count: PtrInt); begin repeat dec(count, 8); dec(P[0], ord(P[0] > deleted)); // branchless code is 10x faster than if :) dec(P[1], ord(P[1] > deleted)); dec(P[2], ord(P[2] > deleted)); dec(P[3], ord(P[3] > deleted)); dec(P[4], ord(P[4] > deleted)); dec(P[5], ord(P[5] > deleted)); dec(P[6], ord(P[6] > deleted)); dec(P[7], ord(P[7] > deleted)); P := @P[8]; until count < 8; while count > 0 do begin dec(count); dec(P[count], ord(P[count] > deleted)); end; end; procedure DynArrayHashTableAdjust16(P: PWordArray; deleted: cardinal; count: PtrInt); begin repeat // branchless code is 10x faster than if :) dec(count, 8); dec(P[0], cardinal(P[0] > deleted)); dec(P[1], cardinal(P[1] > deleted)); dec(P[2], cardinal(P[2] > deleted)); dec(P[3], cardinal(P[3] > deleted)); dec(P[4], cardinal(P[4] > deleted)); dec(P[5], cardinal(P[5] > deleted)); dec(P[6], cardinal(P[6] > deleted)); dec(P[7], cardinal(P[7] > deleted)); P := @P[8]; until count < 8; while count > 0 do begin dec(count); dec(P[count], cardinal(P[count] > deleted)); end; end; {$ifend} procedure ExchgPointer(n1, n2: PPointer); var n: pointer; begin n := n2^; n2^ := n1^; n1^ := n; end; procedure ExchgPointers(n1, n2: PPointer; count: PtrInt); var n: pointer; begin repeat n := n2^; n2^ := n1^; n1^ := n; inc(n1); inc(n2); dec(count); until count = 0; end; procedure ExchgVariant(v1, v2: PPtrIntArray); var c: PtrInt; // 32-bit: 16 bytes = 4 PtrInt; 64-bit: 24 bytes = 3 PtrInt begin c := v2[0]; v2[0] := v1[0]; v1[0] := c; c := v2[1]; v2[1] := v1[1]; v1[1] := c; c := v2[2]; v2[2] := v1[2]; v1[2] := c; {$ifdef CPU32} c := v2[3]; v2[3] := v1[3]; v1[3] := c; {$endif CPU32} end; procedure Exchg(P1, P2: PAnsiChar; count: PtrInt); var i, c: PtrInt; u: AnsiChar; begin i := count shr POINTERSHR; if i <> 0 then repeat c := PPtrInt(P1)^; PPtrInt(P1)^ := PPtrInt(P2)^; PPtrInt(P2)^ := c; inc(P1, SizeOf(c)); inc(P2, SizeOf(c)); dec(i); until i = 0; i := count and POINTERAND; if i <> 0 then repeat dec(i); u := P1[i]; P1[i] := P2[i]; P2[i] := u; until i = 0; end; { ************ Some Convenient TStream descendants } { TStreamWithPosition } {$ifdef FPC} function TStreamWithPosition.GetPosition: Int64; begin result := fPosition; end; {$endif FPC} function TStreamWithPosition.Seek(const Offset: Int64; Origin: TSeekOrigin): Int64; var size: Int64; begin if (Offset <> 0) or (Origin <> soCurrent) then begin size := GetSize; case Origin of soBeginning: result := Offset; soEnd: result := size - Offset; else result := fPosition + Offset; // soCurrent end; if result > size then result := size else if result < 0 then result := 0; fPosition := result; end else // optimize on Delphi when retrieving TStream.Position as Seek(0,soCurrent) result := fPosition; end; function TStreamWithPosition.Seek(Offset: Longint; Origin: Word): Longint; begin result := Seek(Offset, TSeekOrigin(Origin)); // call the 64-bit version above end; { TStreamWithPositionAndSize } function TStreamWithPositionAndSize.GetSize: Int64; begin result := fSize; end; { TRawByteStringStream } constructor TRawByteStringStream.Create(const aString: RawByteString); begin fDataString := aString; end; function TRawByteStringStream.Read(var Buffer; Count: Longint): Longint; begin if Count <= 0 then result := 0 else begin result := Length(fDataString) - fPosition; if result = 0 then exit; if result > Count then result := Count; MoveFast(PByteArray(fDataString)[fPosition], Buffer, result); inc(fPosition, result); end; end; function TRawByteStringStream.GetSize: Int64; begin // faster than the TStream inherited method calling Seek() twice result := length(fDataString); end; procedure TRawByteStringStream.SetSize(NewSize: Longint); begin SetLength(fDataString, NewSize); if fPosition > NewSize then fPosition := NewSize; end; function TRawByteStringStream.Write(const Buffer; Count: Longint): Longint; begin result := Count; if result > 0 then if fDataString = '' then // inlined FastSetString() begin pointer(fDataString) := FastNewString(result, CP_UTF8); MoveFast(Buffer, pointer(fDataString)^, result); fPosition := result; end else begin if fPosition + result > length(fDataString) then SetLength(fDataString, fPosition + result); // resize MoveFast(Buffer, PByteArray(fDataString)[fPosition], result); inc(fPosition, result); end; end; procedure TRawByteStringStream.GetAsText(StartPos, Len: PtrInt; var Text: RawUtf8); var L: PtrInt; begin if StartPos < 0 then StartPos := 0; L := length(fDataString); if (L = 0) or (StartPos >= L) then FastAssignNew(Text) // nothing to return else if (StartPos = 0) and (Len = L) and (PStrCnt(PAnsiChar(pointer(fDataString)) - _STRCNT)^ = 1) then FastAssignUtf8(Text, fDataString) // fast return the fDataString instance else begin dec(L, StartPos); if Len > L then Len := L; // avoid any buffer overflow FastSetString(Text, @PByteArray(fDataString)[StartPos], Len); end; end; procedure TRawByteStringStream.Clear; begin fPosition := 0; fDataString := ''; end; { TSynMemoryStream } constructor TSynMemoryStream.Create(const aText: RawByteString); begin inherited Create; SetPointer(pointer(aText), length(aText)); end; constructor TSynMemoryStream.Create(Data: pointer; DataLen: PtrInt); begin inherited Create; SetPointer(Data, DataLen); end; function TSynMemoryStream.Write(const Buffer; Count: integer): Longint; begin result := RaiseStreamError(self, 'Write'); end; function {%H-}RaiseStreamError(Caller: TObject; const Context: shortstring): PtrInt; begin raise EStreamError.CreateFmt('Unexpected %s.%s', [ClassNameShort(Caller)^, Context]); end; procedure crc32tabInit(polynom: cardinal; var tab: TCrc32tab); var i, n: PtrUInt; crc: cardinal; begin // 256 bytes of code to generate 2 x 8KB lookup tables i := 0; repeat // unrolled branchless root lookup table generation crc := cardinal(-(i and 1) and polynom) xor (i shr 1); crc := cardinal(-(crc and 1) and polynom) xor (crc shr 1); crc := cardinal(-(crc and 1) and polynom) xor (crc shr 1); crc := cardinal(-(crc and 1) and polynom) xor (crc shr 1); crc := cardinal(-(crc and 1) and polynom) xor (crc shr 1); crc := cardinal(-(crc and 1) and polynom) xor (crc shr 1); crc := cardinal(-(crc and 1) and polynom) xor (crc shr 1); crc := cardinal(-(crc and 1) and polynom) xor (crc shr 1); tab[0, i] := crc; if i = 255 then break; inc(i); until false; i := 0; repeat // expand the root lookup table for by-8 fast computation crc := tab[0, i]; for n := 1 to high(tab) do begin crc := (crc shr 8) xor tab[0, ToByte(crc)]; tab[n, i] := crc; end; inc(i); until i > 256; end; procedure InitializeUnit; begin assert(ord(high(TSynLogLevel)) = 31); // initialize internal constants crc32tabInit(2197175160, crc32ctab); // crc32c() reversed polynom crc32tabInit(3988292384, crc32tab); // crc32() = zlib's reversed polynom // setup minimalistic global functions - overriden by other core units VariantClearSeveral := @_VariantClearSeveral; SortDynArrayVariantComp := @_SortDynArrayVariantComp; // initialize CPU-specific asm TestCpuFeatures; end; initialization InitializeUnit; end.