I use this functions since D2007 I got it online, don't remember where.
But now in XE7 it return a compilation error:
"E2107 Operand size mismatch"
function FastCharPos(const aSource : string; const C: Char; StartPos : Integer) : Integer;
var
L : Integer;
begin
//If this assert failed, it is because you passed 0 for StartPos, lowest value is 1 !!
Assert(StartPos > 0);
Result := 0;
L := Length(aSource);
if L = 0 then exit;
if StartPos > L then exit;
Dec(StartPos);
asm
PUSH EDI //Preserve this register
mov EDI, aSource //Point EDI at aSource
add EDI, StartPos
mov ECX, L //Make a note of how many chars to search through
sub ECX, StartPos
mov AL, C //and which char we want :Error -"E2107 Operand size mismatch"
#Loop:
cmp Al, [EDI] //compare it against the SourceString
jz #Found
inc EDI
dec ECX
jnz #Loop
jmp #NotFound
#Found:
sub EDI, aSource //EDI has been incremented, so EDI-OrigAdress = Char pos !
inc EDI
mov Result, EDI
#NotFound:
POP EDI
end;
end;
function FastCharPosNoCase(const aSource : string; C: Char; StartPos : Integer) : Integer;
var
L : Integer;
begin
Result := 0;
L := Length(aSource);
if L = 0 then exit;
if StartPos > L then exit;
Dec(StartPos);
if StartPos < 0 then StartPos := 0;
asm
PUSH EDI //Preserve this register
PUSH EBX
mov EDX, GUpcaseLUT
mov EDI, aSource //Point EDI at aSource
add EDI, StartPos
mov ECX, L //Make a note of how many chars to search through
sub ECX, StartPos
xor EBX, EBX
mov BL, C //:Error -"E2107 Operand size mismatch"
mov AL, [EDX+EBX]
#Loop:
mov BL, [EDI]
inc EDI
cmp Al, [EDX+EBX]
jz #Found
dec ECX
jnz #Loop
jmp #NotFound
#Found:
sub EDI, aSource //EDI has been incremented, so EDI-OrigAdress = Char pos !
mov Result, EDI
#NotFound:
POP EBX
POP EDI
end;
end;
What do I need to update these two functions to XE7 win32?
What must I do?
Thanks.
This code was written for pre Unicode Delphi where Char is an alias for AnsiChar, the 8 bit character type. In Delphi 2009 and later, Char is an alias for WideChar the 16 bit character type.
The reason for the error message is that the code is intended to operate on 8 bit character elements, but you are providing 16 bit operands. The operator expects 8 bit operands, but you supplied 16 bit operands.
Change Char to AnsiChar to make this code compile and behave as intended on all versions of Delphi.
Having said that, I suggest you stop using this code. Instead use Pos. As a rule, it is preferable to use built-in library functions.
You should stop using old assembler version for string routines and use the use built-in library functions.
If you want to move on in a hurry you can reimplement you functions like this:
function FastCharPos(const aSource: string; const C: Char; StartPos: Integer): Integer; inline;
begin
Result := Pos(C, aSource, StartPos);
end;
function FastCharPosNoCase(const aSource: string; C: Char; StartPos: Integer): Integer; inline;
begin
Result := Pos(AnsiUppercase(C), AnsiUppercase(aSource), StartPos);
end;
Related
I am trying to convert some Delphi 5 code to Delphi XE7-x64 and I am stuck on following code:
function FindScanline(Source : Pointer; MaxLen : Cardinal;
Value : Cardinal) : Cardinal; assembler;
asm
PUSH ECX
MOV ECX,EDX
MOV EDX,EDI
MOV EDI,EAX
POP EAX
REPE SCASB
MOV EAX,ECX
MOV EDI,EDX
end;
As far as I understand following things are occuring:
push the contents of ECX register(Value) onto the stack
move contents of EDX register(MaxLen) into ECX register. now ECX holds (MaxLen)
move contents of EDI register into EDX register. now EDX holds (EDI)
move contents of EAX register into EDI register. now EDI holds (Source)
pop ECX into EDX. now EDX holds (Value). Was (EDI) lost?
repeat while equal ?decrement ECX for each char?
move contents of ECX register into EAX register
move contents of EDX register into EDI register
For reference function FindScanline is used in function GetCursorHeightMargin
Any help in translating above will be appreciated.
Here is a literal translation:
function FindScanline(Source: Pointer; MaxLen: Cardinal; Value: Cardinal): Cardinal;
var
Ptr: PByte;
begin
Result := MaxLen;
if Result > 0 then
dec(Result);
Ptr := Source;
while (Result > 0) and (Ptr^ = Value) do
begin
inc(Ptr);
dec(Result);
end;
end;
It's rather messy to handle the edge cases unfortunately.
I am trying to rewrite the TList.IndexOf method in assembler (XE3). Here is my code
function TFastList.IndexOfAsm(Item: Pointer): Integer;
{var
P: PPointer;
begin
P := Pointer(FList);
for Result := 0 to FCount - 1 do
begin
if P^ = Item then
Exit;
Inc(P);
end;
Result := -1;}
var
FCnt, rslt: Integer;
FData: Pointer;
begin
FCnt := Count;
FData := List;
asm
push edi
mov ecx, FCnt
mov edi, FData
mov eax, Item
repne scasd
mov eax, FCnt
sub eax, ecx
dec eax
mov rslt, eax
pop edi
end;
Result := rslt;
end;
Naturally I would like to use the properties like Count or List directly. I understand why the compiler refuses to give access to private fields FCount and FList, but how do I access the corresponding properties? Count, Self.Count, and [eax].Count all give the inline assembler error.
JIC: I don't handle the not found situation here by intent
You can't access the object property via Delphi assembler!
Delphi compiler is good and Delphi compiled code I belive is also very fast.
Your code has mistake because doesn't test zero count velue what should cause memory access violation!
Do not use repne scasd because it is slow.
However you can hack code manualy to make test... :)
function TFastList.IndexOfAsm(Item: Pointer): Integer;
//eax = self
//edx = Item
{var
P: PPointer;
begin
P := Pointer(FList);
for Result := 0 to FCount - 1 do
begin
if P^ = Item then
Exit;
Inc(P);
end;
Result := -1;}
const
FItems = 4; //data offset of FItems
FCount = 8; //data offset of FCount
asm
mov ecx, [eax].FItems //ecx = #FItems
mov eax, [eax].FCount //eax = FCount
dec eax //test zero count!
js #Exit //if count was 0 then exit as -1
#Loop: //repeat
cmp Item, [ecx + eax * 4]
jz #Exit
dec eax
jns #Loop //until eax < 0 (actually -1)
#Exit:
end;
I have the following function that works in Delphi 2006, but under Delphi XE2 it gives either an access violation error or a privileged instruction error when processing RET.
function Q_TrimChar(const S: string; Ch: Char): string;
asm
PUSH ESI
MOV ESI,ECX
TEST EAX,EAX
JE ##qt
MOV ECX,[EAX-4]
TEST ECX,ECX
JE ##qt
PUSH EBX
PUSH EDI
MOV EBX,EAX
MOV EDI,EDX
XOR EDX,EDX
MOV EAX,ESI
CALL System.#LStrFromPCharLen
MOV EDX,EDI
MOV ECX,[EBX-4]
##lp1: CMP DL,BYTE PTR [EBX]
JNE ##ex1
INC EBX
DEC ECX
JNE ##lp1
MOV EDX,[ESI]
JMP ##wq
##ex1: DEC ECX
##lp2: CMP DL,BYTE PTR [EBX+ECX]
JNE ##ex2
DEC ECX
JMP ##lp2
##ex2: MOV EDI,[ESI]
LEA EDX,[EDI+ECX+1]
##lp3: MOV AL,BYTE PTR [EBX+ECX]
MOV BYTE PTR [EDI+ECX],AL
DEC ECX
JNS ##lp3
##wq: MOV EAX,[ESI]
MOV BYTE PTR [EDX],0
SUB EDX,EAX
MOV [EAX-4],EDX
POP EDI
POP EBX
POP ESI
RET
##qt: MOV EAX,ESI
CALL System.#LStrClr
POP ESI
end;
I don't know assembly very well. What is the problem?
I completely agree with David's suggestion to simply code this in Pascal and have upvoted that answer. Unless profiling has indicated that this is a true bottleneck then there's really no need for the ASM. Here are two versions. The first is easier to read but the second is more efficient:
function Q_TrimChar(const S: string; Ch: Char): string;
begin
result := S;
while (result <> '') and (result[1] = Ch) do Delete(Result, 1, 1);
while (result <> '') and (result[Length(Result)] = Ch) do Delete(Result, Length(Result), 1);
end;
function Q_TrimChar(const S: string; Ch: Char): string;
var
First, Last : integer;
begin
First := 1;
Last := Length(S);
while (First < Last) and (S[First] = Ch) do inc(First);
while (Last >= First) and (S[Last] = Ch) do Dec(Last);
Result := copy(S, First, Last-First+1);
end;
Delphi 2006 uses single byte ANSI characters and so string is AnsiString, Char is AnsiChar. On Delphi 2009 and later, two byte Unicode characters are used. This function cannot possibly work on both compilers.
Even the standard hack of using AnsiString and AnsiChar does not work. Most likely the assumptions that this function makes about the RTL implementation are no longer valid in modern Delphi.
I would re-write this function in Pascal and let the compiler do the work. Not only will that be the quickest way to solve your current problem, it will also get you over the hurdle of 64-bit compilation should you ever choose to tackle that.
I am trying to implement FNV hash from http://isthe.com/chongo/tech/comp/fnv/
I converted the PowerBasic's inline asm on that page into Delphi.
function ReadFileToMem(sPath:string):Pointer;
var
hFile: THandle;
pBuffer: Pointer;
dSize: DWORD;
dRead: DWORD;
begin
hFile := CreateFile(PChar(sPath), GENERIC_READ, FILE_SHARE_READ, nil, OPEN_EXISTING, 0, 0);
if hFile <> 0 then
dSize := GetFileSize(hFile, nil);
if dSize <> 0 then
begin
SetFilePointer(hFile, 0, nil, FILE_BEGIN);
GetMem(Result, dSize);
ReadFile(hFile, Result^, dSize, dRead, nil);
if dRead = 0 then
MessageBox(0, PChar('Error reading file.'), PChar('Read Error'), MB_ICONEXCLAMATION)
end;
CloseHandle(hFile);
end;
function GetPointerSize(lpBuffer: Pointer): Cardinal; // Function by ErazerZ
begin
if lpBuffer = nil then
Result := Cardinal(-1)
else
Result := Cardinal(Pointer(Cardinal(lpBuffer) -4)^) and $7FFFFFFC -4;
end;
FUNCTION FNV32( dwOffset : Pointer; dwLen : DWORD; offset_basis : DWORD) : DWORD ;
asm
mov esi, dwOffset //;esi = ptr to buffer
mov ecx, dwLen //;ecx = length of buffer (counter)
mov eax, offset_basis //;set to 2166136261 for FNV-1
mov edi, 16777619//&h01000193 //;FNV_32_PRIME = 16777619
xor ebx, ebx //;ebx = 0
#nextbyte:
mul edi //;eax = eax * FNV_32_PRIME
mov bl, [esi] //;bl = byte from esi
xor eax, ebx //;al = al xor bl
inc esi //;esi = esi + 1 (buffer pos)
dec ecx //;ecx = ecx - 1 (counter)
jnz #nextbyte //;if ecx is 0, jmp to NextByte
mov #result, eax //;else, function = eax
end;
procedure TForm1.Button1Click(Sender: TObject);
var
pFile : Pointer;
hFile : Cardinal;
begin
//Profiler1['Test'].Start;
pFile := ReadFileToMem(fn);
hFile := FNV32(pFile,GetPointerSize(pFile),2166136261);
//Profiler1['Test'].Stop;
//OutputDebugString(pchar(Profiler1['Test'].AsText[tiAll]));
OutputDebugString(pchar(inttostr(hFile)));
end;
If a size of given file is more that 200KB, the output is random (hash) number. Am I missing something?
Your asm code is somewhat buggy, IMHO. It will crash your application, as it is written.
You need to preseve esi/edi/ebx registers
parameters are passed in eax,ecx,edx registers
result is the eax register
Correct way to do it could be (not tested, just written here there):
function fnv32(dwOffset : Pointer; dwLen : DWORD; offset_basis: DWORD) : DWORD ;
asm // eax=dwOffset ecx=dwLen edx=offset_basis -> result in eax
push esi
push edi
mov esi,eax
mov eax,edx
or ecx,ecx
je #z
mov edi,16777619
xor edx,edx
#1:
mul edi
mov dl,[esi]
xor eax,edx
inc esi
dec ecx
jnz #1
#z:
pop edi
pop esi
end;
So to read and hash any file, in a pure Delphi way (don't use Windows API like you did):
function fnv32file(const aFileName: TFileName): DWORD;
begin
with TMemoryStream.Create do
try
LoadFromFile(aFileName);
result := fnv32(Memory,Size,0);
finally
Free;
end;
end;
A pure pascal version won't be much slower IMHO (the bottleneck is definitively reading the data from the hard drive):
function fnv32(dwOffset : PByteArray; dwLen : DWORD; offset_basis: DWORD): DWORD ;
var i: integer;
begin
result := offset_basis;
for i := 0 to dwLen-1 do
result := (result*16777619) xor DWORD(dwOffset^[i]);
end;
Where should I start ...
1) CreateFile returns INVALID_HANDLE_VALUE on failure, not 0.
2) SetFilePointer is not necessary.
3) What if you have to hash 16 GB file?
4) You are not releasing allocated memory - FreeMem(pFile).
5) GetPointerSize is a total hack. You could just return file size from ReadFileToMem.
The following code is a rewrite of your approach. It still loads complete file into the memory but is implemented "the Delphi way".
function ReadFileToMem(const sPath: string; var buffer: TMemoryStream): boolean;
var
fileStr: TFileStream;
begin
Result := false;
try
fileStr := TFileStream.Create(sPath, fmOpenRead);
try
buffer.Size := 0;
buffer.CopyFrom(fileStr, 0);
finally FreeAndNil(fileStr); end;
Result := true;
except
on E: EFOpenError do
ShowMessage('Error reading file. ' + E.Message);
end;
end;
function FNV32(dwOffset: pointer; dwLen: cardinal; offset_basis: cardinal): cardinal;
asm
mov esi, dwOffset //;esi = ptr to buffer
mov ecx, dwLen //;ecx = length of buffer (counter)
mov eax, offset_basis //;set to 2166136261 for FNV-1
mov edi, 16777619//&h01000193 //;FNV_32_PRIME = 16777619
xor ebx, ebx //;ebx = 0
#nextbyte:
mul edi //;eax = eax * FNV_32_PRIME
mov bl, [esi] //;bl = byte from esi
xor eax, ebx //;al = al xor bl
inc esi //;esi = esi + 1 (buffer pos)
dec ecx //;ecx = ecx - 1 (counter)
jnz #nextbyte //;if ecx is 0, jmp to NextByte
mov #result, eax //;else, function = eax
end;
procedure TForm16.Button1Click(Sender: TObject);
var
hFile : cardinal;
memBuf: TMemoryStream;
begin
memBuf := TMemoryStream.Create;
try
if ReadFileToMem('SomeFile', memBuf) then begin
hFile := FNV32(memBuf.Memory, memBuf.Size, 2166136261);
ShowMessageFmt('Hash = %d', [hFile]);
end;
finally FreeAndNil(memBuf); end;
end;
Enjoy
function fnv(dwOffset : Pointer; dwLen : NativeUInt; offset_basis: NativeUInt) : NativeUInt ;
//
// http://find.fnvhash.com/ - FNV Hash Calculator Online
// http://www.isthe.com/chongo/tech/comp/fnv/
//
// The offset_basis for FNV-1 is dependent on n, the size of the hash:
// 32 bit offset_basis = 2166136261
// 64 bit offset_basis = 14695981039346656037
//
{$IF Defined(CPUX86)}
asm
push ebp
push edi
push ebx // statement must preserve the EDI, ESI, ESP, EBP, and EBX registers
mov ebp, edx
mov edx, ecx // but can freely modify the EAX, ECX, and EDX registers
mov ecx, eax
mov eax, edx
mov edi, 01000193h
xor ebx, ebx
##nexta:
mov bl, byte ptr [ecx]
xor eax, ebx
mul edi
inc ecx
dec ebp
jnz ##nexta
pop ebx
pop edi
pop ebp
end;
{$ELSEIF Defined(CPUX64)}
asm
mov rax, R8
mov r8, rdx
mov r9, 100000001b3h
xor r10, r10
##nexta:
mov r10b, byte ptr [rcx]
xor rax, r10
mul r9
inc rcx
dec r8
jnz ##nexta
end;
{$IFEND}
In Delphi, the declaration of the DivMod function is
procedure DivMod(Dividend: Cardinal; Divisor: Word;
var Result, Remainder: Word);
Thus, the divisor, result, and remainder cannot be grater than 65535, a rather severe limitation. Why is this? Why couldn't the delcaration be
procedure DivMod(Dividend: Cardinal; Divisor: Cardinal;
var Result, Remainder: Cardinal);
The procedure is implemented using assembly, and is therefore probably extremely fast. Would it not be possible for the code
PUSH EBX
MOV EBX,EDX
MOV EDX,EAX
SHR EDX,16
DIV BX
MOV EBX,Remainder
MOV [ECX],AX
MOV [EBX],DX
POP EBX
to be adapted to cardinals? How much slower is the naïve attempt
procedure DivModInt(const Dividend: integer; const Divisor: integer; out result: integer; out remainder: integer);
begin
result := Dividend div Divisor;
remainder := Dividend mod Divisor;
end;
that is not (?) limited to 16-bit integers?
Such a procedure is possible. I have not tested the code enough, but I think it's OK:
procedure DivMod32(Dividend, Divisor: Cardinal; var Quotient, Remainder: Cardinal);
asm
PUSH EBX
MOV EBX,EDX
XOR EDX,EDX
DIV EBX
MOV [ECX],EAX
MOV EBX,Remainder
MOV [EBX],EDX
POP EBX
end;
Updated:
even more efficient:
function DivMod32(Dividend, Divisor: Cardinal; var Remainder: Cardinal): Cardinal;
asm
PUSH EBX
MOV EBX,EDX
XOR EDX,EDX
DIV EBX
MOV [ECX],EDX
POP EBX
end;
Updated 2:
You can see the assembly code generated by Delphi compiler in the Disassembly (or CPU) window. Eg, the procedure
procedure DivMod32(const Dividend: Cardinal; const Divisor: Cardinal;
out result: Cardinal; out remainder: Cardinal);
begin
result := Dividend div Divisor;
remainder := Dividend mod Divisor;
end;
generates code
Unit1.pas.28: begin
0046CC94 55 push ebp
0046CC95 8BEC mov ebp,esp
0046CC97 53 push ebx
0046CC98 56 push esi
0046CC99 8BF2 mov esi,edx
0046CC9B 8BD8 mov ebx,eax
Unit1.pas.29: result := Dividend div Divisor;
0046CC9D 8BC3 mov eax,ebx
0046CC9F 33D2 xor edx,edx
0046CCA1 F7F6 div esi
0046CCA3 8901 mov [ecx],eax
Unit1.pas.30: remainder := Dividend mod Divisor;
0046CCA5 8BC3 mov eax,ebx
0046CCA7 33D2 xor edx,edx
0046CCA9 F7F6 div esi
0046CCAB 8B4508 mov eax,[ebp+$08]
0046CCAE 8910 mov [eax],edx
Unit1.pas.31: end;
0046CCB0 5E pop esi
0046CCB1 5B pop ebx
0046CCB2 5D pop ebp
0046CCB3 C20400 ret $0004
This code is linear (contains no jumps) and modern processors (with long instruction pipeline) are very efficient in executing linear code. So though my DivMode32 implementation is about 3 times shorter, 60% is a reasonable estimate.