Sign in Join

Live chat for Free Pascal, Lazarus, Delphi and every Pascal programmer.

Mailing lists / lazarus / 2021-12

UTF8LengthFast returning incorrect results on AARCH64 (MacOS)

47 messages · 9 people · –

Reply by email Subscribe to this list Your subscription

Read-only archive. Use j and k to move between messages.

Noel Duffy

I need some help getting to the root of a problem with incorrect results on Apple hardware (M1, aarch64) for the function UTF8LengthFast in lazutf8.

On MacOS, when given a string containing one or more UTF8 characters, UTF8LengthFast returns wildly incorrect results. On Fedora, the function returns the correct answer.

On Apple, I'm using fpc 3.3.1, and Lazarus is 2.2.0RC3. On Fedora, Lazarus is 2.0.12-2, and fpc is 3.2.2-1.

The following small program demonstrates the problem here.

% cat utf8len.pas

program utf8len;
{$mode objfpc}{$H+}
{$CODEPAGE UTF8}
uses SysUtils, lazutf8;
const
   s =  '€';
var
   n: PtrInt;
begin
   n := UTF8LengthFast(s);
   writeln('Len='+inttostr(n));
end.

% file utf8len.pas
utf8len.pas: Unicode text, UTF-8 text

To compile this, on MacOS I use this:

% ~/fpc3.3.1/bin/fpc -Sh -Cro -O3 -XX -vewbq -FU.
-Fu/usr/local/share/lazarus/components/lazutils/lib/aarch64-darwin utf8len.pas

On Fedora, with this:

$ /usr/bin/fpc -Sh -Cro -O3 -XX -vewbq -FU.
-Fu/usr/lib64/lazarus/components/lazutils utf8len.pas

Then run it:

On MacOS:

% ./utf8len
Len=-100663283

On Fedora:

$ ./utf8len
Len=1

On MacOS, I built fpc from source, compiling 3.3.1 with version 3.2.2. I then compiled Lazarus using fpc 3.3.1.

Because I built fpc and then Lazarus, I'm considering the possibility I introduced an error or a bug somewhere, so I want to eliminate that possibility if possible by asking if anyone else can reproduce this problem?

I have traced through the code using a debugger on both platforms. The same path through the function UTF8LengthFast is followed, but the final loop involving boolean shifting of bytes produces different results. I don't understand well enough the algorithm that the function uses to easily see what's going on.

Reply by email

Bart
to Noel Duffy
AM Noel Duffy wrote
I need some help getting to the root of a problem with incorrect results on Apple hardware (M1, aarch64) for the function UTF8LengthFast in lazutf8.

Your M1 architecture is BigEndian perhaps?
(I really have no idea)

Bart

Reply by email

Juha Manninen
to Noel Duffy: I need some help getting to the root of a problem with incorrect results on Apple…

On Mon, Dec 27, 2021 at 1:44 AM Noel Duffy via lazarus <

Show quoted text 9 lines
wrote: I need some help getting to the root of a problem with incorrect results on Apple hardware (M1, aarch64) for the function UTF8LengthFast in lazutf8. On MacOS, when given a string containing one or more UTF8 characters, UTF8LengthFast returns wildly incorrect results. On Fedora, the function returns the correct answer.

You mean both MacOS and Fedora run on the same aarch64 CPU? It must be a Big endian / Little endian issue. IIRC it can be adjusted in ARM CPUs.
Why do MacOS and Linux use a different setting there? I have no idea.

If somebody can figure out how to port the function, good. Otherwise we can make UTF8LengthFast call the standard UTF8Length using an IFDEF when needed. What is the correct define then?

Juha
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.lazarus-ide.org/pipermail/lazarus/attachments/20211227/8fb1a246/attachment.htm>

Reply by email

Bart
to Juha Manninen
PM Juha Manninen wrote
It must be a Big endian / Little endian issue. IIRC it can be adjusted in ARM CPUs. Why do MacOS and Linux use a different setting there? I have no idea.

On second thought: if the function returns grabage for just a single '?', the code for that should not enter the pasrt where it handles blocks of size PtrInt and does masking with EIGHTYMASK etc. (The part of the code that might be endianness dependant).
It should go to one of the 2 loops that simply does: Result += (pn8^ shr 7) and ((not pn8^) shr 6);
That part should not depend on Win32 a sigle '?' will result in something like this:

pn8^ =11100010 //first byte
(pn8^ shr 7) =11111111 //<<-- I would have expected that to be 00000001 ? (not pn8^) =00011101
(not pn8^) shr 6 =00000000
Add: (pn8^ shr 7) and ((not pn8^) shr 6)=0

pn8^ =10000010 //second byte
(pn8^ shr 7) =11111111
(not pn8^) =01111101
(not pn8^) shr 6 =00000001
Add: (pn8^ shr 7) and ((not pn8^) shr 6)=1

pn8^ =10101100 //third and last byte of '?' (pn8^ shr 7) =11111111
(not pn8^) =01010011
(not pn8^) shr 6 =00000001
Add: (pn8^ shr 7) and ((not pn8^) shr 6)=1

B.t.w.
I find the code in Utf8LengthFast difficult to read. Personally I dislike the C-ism of += and >> (even more so if both >> and shr is used).

Bart

Reply by email

Marco van de Voort
to Bart: On Mon, Dec 27, 2021 at 3:41 PM Juha Manninen via lazarus On second thought: if the…

Op 12/27/2021 om 4:39 PM schreef Bart via lazarus:

pn8^ =11100010 //first byte (pn8^ shr 7) =11111111 //<<-- I would have expected that to be 00000001 ?

Depends on if pn8^ is signed or not, for a signed shift it makes sense. The definition as pint8 (instead of puint8) is an odd choice.

The expression seems to be 1 when the top bits are 10? iow when it is a follow bytes of utf8, that is what the comment says, and I as far as I can see the signedness doesn't matter.

Basically to me that seems to be a branchless version of

if (p[i] and %11000000)=%10000000 then
 ?? inc(result);

...which counts all utf8 follow bytes, and then subtracts it from the number of bytes in a string to find the number of utf8 sequences/codepoints.

Maybe the absolute stuff confuses somehow? Also make sure the input is 100% the same by printing the values of the bytes of the input string.

Reply by email

Bart
to Marco van de Voort
PM Marco van de Voort wrote 9 lines
The expression seems to be 1 when the top bits are 10 iow when it is a follow bytes of utf8, that is what the comment says, and I as far as I can see the signedness doesn't matter. Basically to me that seems to be a branchless version of if (p[i] and %11000000)=%10000000 then inc(result);

This is how I understood that paert of the code as well.

Just a side node: all this assumes that the UTF8 is correct (in the strict sense).

Now for the part that tries to do calculations on blocks of 32 or 64 bits. It uses a multiplication in that part.
That seems a bit odd as this code tries to do evrything with ands, nots, and shifts.

Would this approach (in that part of the code) also work? X = 32 or 64 bit block

1: AND X with EIGHTYMASK
2: SHR 7: gives (A)

3: NOT X
4: SHR 6: gives (B)

5: (A) AND (B): gives (C)

Basically we do the same as for a 1-byte block;
Now we have a pattern where any 1 tells us this byte was a following byte

A 32-bit example:
(Invalid sequence but that does not matter here)

X = %11xxxxxx %01xxxxxx %00xxxxxx %10xxxxxx
(Leading-ASCII-ASCII-Following, so count of following bytes must be 1)

AND with EIGTHYMASK gives %10000000 %00000000 %00000000 %10000000 SHR 7 gives: %00000001 %00000000 %00000000 %00000001 (A)

NOT %11xxxxxx %01xxxxxx %00xxxxxx %10xxxxxx = %00yyyyyy %10yyyyyy %11yyyyyy %01yyyyyy
SHR 6 gives: %00000000 %yyyyyy10 %yyyyyy11 %yyyyyy01 (B)

(A) and (B) gives: %00000000 %00000000 %00000000 %00000001 (C) All non-following bytes turn into %00000000

The count of following bytes is PopCnt(C)

As long as PopCnt is available as a machine instruction, this will be faster than the (nx * ONEMASK) calculation I think. (I would hope this is the case for all platforms Lazarus supports, if not the call to PopCnt could be ifdef-ed.)

So basically change this:

    nx := ((pnx^ and EIGHTYMASK) shr 7) and ((not pnx^) shr 6);
    {$push}{$overflowchecks off} // "nx * ONEMASK" causes an
arithmetic overflow.
    Result += (nx * ONEMASK) >> ((sizeof(PtrInt) - 1) * 8);
    {$pop}

into

    nx := ((pnx^ and EIGHTYMASK) shr 7) and ((not pnx^) shr 6);
    Result := Result + PopCnt(PtrUInt(nx));  /PopCnt only accepts
unsigend parameter

Since bit manipulating is not my strongpoint, please comment.

Bart

Reply by email

Noel Duffy
to Bart: On Mon, Dec 27, 2021 at 3:41 PM Juha Manninen via lazarus On second thought: if the…
Bart wrote 9 lines
On Mon, Dec 27, 2021 at 3:41 PM Juha Manninen via lazarus wrote: > It must be a Big endian / Little endian issue. IIRC it can be adjusted in ARM CPUs. > Why do MacOS and Linux use a different setting there? I have no idea. On second thought: if the function returns grabage for just a single '?', the code for that should not enter the pasrt where it handles blocks of size PtrInt and does masking with EIGHTYMASK etc.

It's not just the euro, though. It's any utf-8 sequence.

Reply by email

Noel Duffy
to Juha Manninen: On Mon, Dec 27, 2021 at 1:44 AM Noel Duffy via lazarus < You mean both MacOS and Fedora…
Juha Manninen wrote 12 lines
On Mon, Dec 27, 2021 at 1:44 AM Noel Duffy via lazarus < > wrote: > I need some help getting to the root of a problem with incorrect results > on Apple hardware (M1, aarch64) for the function UTF8LengthFast in lazutf8. > > On MacOS, when given a string containing one or more UTF8 characters, > UTF8LengthFast returns wildly incorrect results. On Fedora, the function > returns the correct answer. > You mean both MacOS and Fedora run on the same aarch64 CPU?

Oh no, Fedora runs on an Intel x86_64. I don't think there's a Fedora that runs on aarch64. I included the results from Fedora just to show that the code does work in some places.

It must be a Big endian / Little endian issue. IIRC it can be adjusted in ARM CPUs. Why do MacOS and Linux use a different setting there? I have no idea.

Well, as Florian said above, the M1 is little-endian. So it doesn't appear to be an endian issue.

https://developer.apple.com/documentation/apple-silicon/porting-your-macos-apps-to-apple-silicon

Reply by email

Bart
to Noel Duffy: It's not just the euro, though. It's any utf-8 sequence.
PM Noel Duffy wrote
It's not just the euro, though. It's any utf-8 sequence.

What I meant was that a single '?' (or any other single UTF8 "character") will not enter the mentioned block.
Can you add some debug statements to display the values of the it uses in the calculation like I did in the 4th message in this thread?

function UTF8LengthFast(p: PChar; ByteCount: PtrInt): PtrInt;
const
{$ifdef CPU32}
  ONEMASK   =$01010101;
  EIGHTYMASK=$80808080;
{$endif}
{$ifdef CPU64}
  ONEMASK   =$0101010101010101;
  EIGHTYMASK=$8080808080808080;
{$endif}
var
  pnx: PPtrInt absolute p; // To get contents of text in PtrInt
blocks. x refers to 32 or 64 bits
  pn8: pint8 absolute pnx; // To read text as Int8 in the initial and
final loops
  ix: PtrInt absolute pnx; // To read text as PtrInt in the block loop
  nx: PtrInt;              // values processed in block loop
  i,cnt,e: PtrInt;
begin
  Result := 0;
  e := ix+ByteCount; // End marker
  // Handle any initial misaligned bytes.
  cnt := (not (ix-1)) and (sizeof(PtrInt)-1);
  if cnt>ByteCount then
    cnt := ByteCount;
  for i := 1 to cnt do
  begin
    // Is this byte NOT the first byte of a character?
    writeln('pn8^             = ',byte(pn8^).ToBinString);
    writeln('pn8^ shr 7       = ',Byte(Byte(pn8^) shr 7).ToBinString);
    writeln('not pn8^         = ',Byte(not pn8^).ToBinString);
    writeln('(not pn8^) shr 6 = ',Byte((not pn8^) shr 6).ToBinString);
    writeln;
    Result += (pn8^ shr 7) and ((not pn8^) shr 6);
    inc(pn8);
  end;
  // Handle complete blocks
  for i := 1 to (ByteCount-cnt) div sizeof(PtrUInt) do
  begin
    // Count bytes which are NOT the first byte of a character.
    {
    nx := ((pnx^ and EIGHTYMASK) shr 7) and ((not pnx^) shr 6);
    {$push}{$overflowchecks off} // "nx * ONEMASK" causes an
arithmetic overflow.
    Result += (nx * ONEMASK) >> ((sizeof(PtrInt) - 1) * 8);
    {$pop}
    }
    nx := ((pnx^ and EIGHTYMASK) shr 7) and ((not pnx^) shr 6);
    Result := Result + PopCnt(PtrUInt(nx));
    inc(pnx);
  end;
  // Take care of any left-over bytes.
  while ix<e do
  begin
    // Is this byte NOT the first byte of a character?
    writeln('pn8^             = ',byte(pn8^).ToBinString);
    writeln('pn8^ shr 7       = ',Byte(Byte(pn8^) shr 7).ToBinString);
    writeln('not pn8^         = ',Byte(not pn8^).ToBinString);
    writeln('(not pn8^) shr 6 = ',Byte((not pn8^) shr 6).ToBinString);
    writeln;
    //writeln('',);
    Result += (pn8^ shr 7) and ((not pn8^) shr 6);
    inc(pn8);
  end;
  Result := ByteCount - Result;
end;

(Just put this in the main unit, no need to change and rebuild LazUtf8 unit) Make sure you app has a console.
Then just do something like:

  S := '?';
  Len := Utf8LengthFast(PChar(S), Length(S));
It should output somthing like;

pn8^ = 11100010
pn8^ shr 7 = 00000001
not pn8^ = 00011101
(not pn8^) shr 6 = 00000000

pn8^ = 10000010
pn8^ shr 7 = 00000001
not pn8^ = 01111101
(not pn8^) shr 6 = 00000001

pn8^ = 10101100
pn8^ shr 7 = 00000001
not pn8^ = 01010011
(not pn8^) shr 6 = 00000001

Notice that '?' in UTF8 is the byte sequnce 11100010 10000010 10101100 (the values you should see in pn8^.

Bart

Reply by email

Noel Duffy
to Bart: On Mon, Dec 27, 2021 at 6:35 PM Marco van de Voort via lazarus This is how I understood…
Bart wrote 77 lines
On Mon, Dec 27, 2021 at 6:35 PM Marco van de Voort via lazarus wrote: > The expression seems to be 1 when the top bits are 10 iow when it is a > follow bytes of utf8, that is what the comment says, and I as far as I > can see the signedness doesn't matter. > > Basically to me that seems to be a branchless version of > > if (p[i] and %11000000)=%10000000 then > > inc(result); This is how I understood that paert of the code as well. Just a side node: all this assumes that the UTF8 is correct (in the strict sense). Now for the part that tries to do calculations on blocks of 32 or 64 bits. It uses a multiplication in that part. That seems a bit odd as this code tries to do evrything with ands, nots, and shifts. Would this approach (in that part of the code) also work? X = 32 or 64 bit block 1: AND X with EIGHTYMASK 2: SHR 7: gives (A) 3: NOT X 4: SHR 6: gives (B) 5: (A) AND (B): gives (C) Basically we do the same as for a 1-byte block; Now we have a pattern where any 1 tells us this byte was a following byte A 32-bit example: (Invalid sequence but that does not matter here) X = %11xxxxxx %01xxxxxx %00xxxxxx %10xxxxxx (Leading-ASCII-ASCII-Following, so count of following bytes must be 1) AND with EIGTHYMASK gives %10000000 %00000000 %00000000 %10000000 SHR 7 gives: %00000001 %00000000 %00000000 %00000001 (A) NOT %11xxxxxx %01xxxxxx %00xxxxxx %10xxxxxx = %00yyyyyy %10yyyyyy %11yyyyyy %01yyyyyy SHR 6 gives: %00000000 %yyyyyy10 %yyyyyy11 %yyyyyy01 (B) (A) and (B) gives: %00000000 %00000000 %00000000 %00000001 (C) All non-following bytes turn into %00000000 The count of following bytes is PopCnt(C) As long as PopCnt is available as a machine instruction, this will be faster than the (nx * ONEMASK) calculation I think. (I would hope this is the case for all platforms Lazarus supports, if not the call to PopCnt could be ifdef-ed.) So basically change this: nx := ((pnx^ and EIGHTYMASK) shr 7) and ((not pnx^) shr 6); {$push}{$overflowchecks off} // "nx * ONEMASK" causes an arithmetic overflow. Result += (nx * ONEMASK) >> ((sizeof(PtrInt) - 1) * 8); {$pop} into nx := ((pnx^ and EIGHTYMASK) shr 7) and ((not pnx^) shr 6); Result := Result + PopCnt(PtrUInt(nx)); /PopCnt only accepts unsigend parameter Since bit manipulating is not my strongpoint, please comment.

I ran my code that calls UTF8LengthFast with a euro through the debugger to see what path through UTF8LengthFast was followed. The debugger skipped over all the loops until the very last one:

   // Take care of any left-over bytes.
   while ix<e do
   begin
     // Is this byte NOT the first byte of a character?
     Result += (pn8^ shr 7) and ((not pn8^) shr 6);
     inc(pn8);
   end;
   Result := ByteCount - Result;

The euro symbol encoded in UTF8 has three bytes, 226, 130, 172.

The loop has 3 iterations, one for each byte:

0: Result=33554428
1: Result=67108857
2: Result=100663286

The last line subtracts Result from ByteCount, and gets us -100663283.

OK, time to simplify. I tried writing a short pascal program that did the bit shifting with constants:

    c := (226 shr 7) and ((not 226) shr 6);
    writeln('c='+inttostr(c)+' 226.');
    c := (130 shr 7) and ((not 130) shr 6);
    writeln('c='+inttostr(c)+' 130.');
    c := (172 shr 7) and ((not 172) shr 6);
    writeln('c='+inttostr(c)+' 172.');

This produces the correct result:

c=0 226.
c=1 130.
c=1 172.

Next I tried using a pint8:

const
    s = '?';
var
   p: PChar;
   c: Byte;
   pn8: pint8 absolute p;
begin
    c := (pn8^ shr 7) and ((not pn8^) shr 6);
    writeln('c='+inttostr(c)+' '+IntToStr(Byte(pn8^)));
    Inc(pn8);
    c := (pn8^ shr 7) and ((not pn8^) shr 6);
    writeln('c='+inttostr(c)+' '+IntToStr(Byte(pn8^)));
    Inc(pn8);
    c := (pn8^ shr 7) and ((not pn8^) shr 6);
    writeln('c='+inttostr(c)+' '+IntToStr(Byte(pn8^)));
end;

This produces:

c=252 226
c=253 130
c=253 172

Clearly wrong. Someone mentioned signed vs unsigned, so that seemed a logical next step. I tried using a pbyte.

const
    s = '?';
var
   pb: PByte absolute p;
   c: Byte;
   p: PChar;
begin
    p := PChar(s);
    c := (pb^ shr 7) and ((not pb^) shr 6);
    writeln('c='+inttostr(c)+' '+IntToStr(pb^));
    Inc(pb);
    c := (pb^ shr 7) and ((not pb^) shr 6);
    writeln('c='+inttostr(c)+' '+IntToStr(pb^));
    Inc(pb);
    c := (pb^ shr 7) and ((not pb^) shr 6);
    writeln('c='+inttostr(c)+' '+IntToStr(pb^));
end;

This produces:

c=0 226
c=1 130
c=1 172

Correct result. So it appears that using a pint8 produces the wrong result on aarch64, but it doesn't on x86_64. It's not clear why, though.

So it appears to me that an unsigned pointer type is required in UTFLengthFast.

Reply by email

Juha Manninen
to Noel Duffy

On Tue, Dec 28, 2021 at 3:29 AM Noel Duffy via lazarus <

Show quoted text 5 lines
wrote: So it appears to me that an unsigned pointer type is required in UTFLengthFast.

Can you please create a patch for UTFLengthFast. You can upload it here or create a merge request in GitLab or anything.

Juha
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.lazarus-ide.org/pipermail/lazarus/attachments/20211228/af0a26be/attachment.htm>

Reply by email

Martin Frb
to Juha Manninen
Juha Manninen wrote 10 lines
On Tue, Dec 28, 2021 at 3:29 AM Noel Duffy via lazarus wrote: So it appears to me that an unsigned pointer type is required in UTFLengthFast. Can you please create a patch for UTFLengthFast. You can upload it here or create a merge request in GitLab or anything.

I would like to see the generates assembler on M1, if that is possible?? (for code with optimization off, as well as code with whatever optimization was used so far)
Thanks

None the less, switching to "puint8" might be a good idea. -------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.lazarus-ide.org/pipermail/lazarus/attachments/20211228/8cf98192/attachment.htm>

Reply by email

Bart
to Juha Manninen: On Tue, Dec 28, 2021 at 3:29 AM Noel Duffy via lazarus < Can you please create a patch…
AM Juha Manninen wrote
Can you please create a patch for UTFLengthFast. You can upload it here or create a merge request in GitLab or anything.

@Juha: can you please comment on my possible improvement using PopCnt instead of a multiplication with ONEMASK.

I will patch the function using unsigned types where applicable. I will keep the loop variables unsigned though.

Bart

Reply by email

Juha Manninen
to Bart

On Tue, Dec 28, 2021 at 1:45 PM Bart via lazarus <

Show quoted text 5 lines
wrote: @Juha: can you please comment on my possible improvement using PopCnt instead of a multiplication with ONEMASK.

I confess I didn't remember what PopCnt does. I checked from the net. FPC implements it as internproc.

  function PopCnt(Const AValue : QWord): QWord;[internproc:fpc_in_popcnt_x];

I guess it translates to one x86_64 instruction.
Is it implemented for all CPUs? I found this:
https://gitlab.com/freepascal.org/fpc/source/-/issues/38729 If it works everywhere, good. It looks like another good optimization for this highly optimized function.

I will patch the function using unsigned types where applicable.

I will keep the loop variables unsigned though.

Yes, thank you.

Regards,
Juha
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.lazarus-ide.org/pipermail/lazarus/attachments/20211228/ba032b2a/attachment.htm>

Reply by email

Bart
to Martin Frb: I would like to see the generates assembler on M1, if that is possible?? (for code with…
PM Martin Frb wrote
I would like to see the generates assembler on M1, if that is possible? (for code with optimization off, as well as code with whatever optimization was used so far)

@Noel:

Here's example code (standalone) you can use to test both signed and unsigned versions.
Save this code as ulen.pas
In order to get assembler output compile with:

fpc -al ulen.pas

This will produce the file ulen.s
You can attach or copy that here.

=============

program ulen;
{$mode objfpc}{$H+}
{$optimization off}
{$codepage utf8}
uses
  SysUtils;
function UTF8LengthFast_Signed(p: PChar; ByteCount: PtrInt): PtrInt;
const
{$ifdef CPU32}
  ONEMASK   =$01010101;
  EIGHTYMASK=$80808080;
{$endif}
{$ifdef CPU64}
  ONEMASK   =$0101010101010101;
  EIGHTYMASK=$8080808080808080;
{$endif}
var
  pnx: PPtrInt absolute p; // To get contents of text in PtrInt
blocks. x refers to 32 or 64 bits
  pn8: pint8 absolute pnx; // To read text as Int8 in the initial and
final loops
  ix: PtrInt absolute pnx; // To read text as PtrInt in the block loop
  nx: PtrInt;              // values processed in block loop
  i,cnt,e: PtrInt;
begin
  Result := 0;
  e := ix+ByteCount; // End marker
  // Handle any initial misaligned bytes.
  cnt := (not (ix-1)) and (sizeof(PtrInt)-1);
  if cnt>ByteCount then
    cnt := ByteCount;
  for i := 1 to cnt do
  begin
    // Is this byte NOT the first byte of a character?
    //writeln('pn8^             = ',byte(pn8^).ToBinString);
    //writeln('pn8^ shr 7       = ',Byte(Byte(pn8^) shr 7).ToBinString);
    //writeln('not pn8^         = ',Byte(not pn8^).ToBinString);
    //writeln('(not pn8^) shr 6 = ',Byte((not pn8^) shr 6).ToBinString);
    //writeln;
    Result += (pn8^ shr 7) and ((not pn8^) shr 6);
    inc(pn8);
  end;
  // Handle complete blocks
  for i := 1 to (ByteCount-cnt) div sizeof(PtrUInt) do
  begin
    // Count bytes which are NOT the first byte of a character.
    nx := ((pnx^ and EIGHTYMASK) shr 7) and ((not pnx^) shr 6);
    {$push}{$overflowchecks off} // "nx * ONEMASK" causes an
arithmetic overflow.
    Result += (nx * ONEMASK) >> ((sizeof(PtrInt) - 1) * 8);
    {$pop}
    inc(pnx);
  end;
  // Take care of any left-over bytes.
  while ix<e do
  begin
    // Is this byte NOT the first byte of a character?
    //writeln('pn8^             = ',byte(pn8^).ToBinString);
    //writeln('pn8^ shr 7       = ',Byte(Byte(pn8^) shr 7).ToBinString);
    //writeln('not pn8^         = ',Byte(not pn8^).ToBinString);
    //writeln('(not pn8^) shr 6 = ',Byte((not pn8^) shr 6).ToBinString);
    //writeln;
    //writeln('',);
    Result += (pn8^ shr 7) and ((not pn8^) shr 6);
    inc(pn8);
  end;
  Result := ByteCount - Result;
end;
function UTF8LengthFast_Unsigned(p: PChar; ByteCount: PtrInt): PtrInt;
const
{$ifdef CPU32}
  ONEMASK   =$01010101;
  EIGHTYMASK=$80808080;
{$endif}
{$ifdef CPU64}
  ONEMASK   =$0101010101010101;
  EIGHTYMASK=$8080808080808080;
{$endif}
var
  pnx: PPtrUInt absolute p; // To get contents of text in PtrInt
blocks. x refers to 32 or 64 bits
  pn8: puint8 absolute pnx; // To read text as Int8 in the initial and
final loops
  ix: PtrUInt absolute pnx; // To read text as PtrInt in the block loop
  nx: PtrUInt;              // values processed in block loop
  i,cnt,e: PtrInt;
begin
  Result := 0;
  e := ix+PtrUInt(ByteCount); // End marker
  // Handle any initial misaligned bytes.
  cnt := (not (ix-1)) and (sizeof(PtrInt)-1);
  if cnt>ByteCount then
    cnt := ByteCount;
  for i := 1 to cnt do
  begin
    // Is this byte NOT the first byte of a character?
    //writeln('pn8^             = ',byte(pn8^).ToBinString);
    //writeln('pn8^ shr 7       = ',Byte(Byte(pn8^) shr 7).ToBinString);
    //writeln('not pn8^         = ',Byte(not pn8^).ToBinString);
    //writeln('(not pn8^) shr 6 = ',Byte((not pn8^) shr 6).ToBinString);
    //writeln;
    Result += (pn8^ shr 7) and ((not pn8^) shr 6);
    inc(pn8);
  end;
  // Handle complete blocks
  for i := 1 to (ByteCount-cnt) div sizeof(PtrUInt) do
  begin
    // Count bytes which are NOT the first byte of a character.
    nx := ((pnx^ and EIGHTYMASK) shr 7) and ((not pnx^) shr 6);
    {$push}{$overflowchecks off} // "nx * ONEMASK" causes an
arithmetic overflow.
    Result += (nx * ONEMASK) >> ((sizeof(PtrInt) - 1) * 8);
    {$pop}
    inc(pnx);
  end;
  // Take care of any left-over bytes.
  while ix<e do
  begin
    // Is this byte NOT the first byte of a character?
    //writeln('pn8^             = ',byte(pn8^).ToBinString);
    //writeln('pn8^ shr 7       = ',Byte(Byte(pn8^) shr 7).ToBinString);
    //writeln('not pn8^         = ',Byte(not pn8^).ToBinString);
    //writeln('(not pn8^) shr 6 = ',Byte((not pn8^) shr 6).ToBinString);
    //writeln;
    //writeln('',);
    Result += (pn8^ shr 7) and ((not pn8^) shr 6);
    inc(pn8);
  end;
  Result := ByteCount - Result;
end;
var
  Len: PtrInt;
  Euro: String;
begin
  DefaultSystemCodePage := CP_UTF8;
  Euro := '?';
  writeln('Signed version');
  Len := Utf8LengthFast_Signed(PChar(Euro), Length(Euro));
  writeln('Len = ',Len);
  writeln('Unsigned version');
  Len := Utf8LengthFast_Unsigned(PChar(Euro), Length(Euro));
  writeln('Len = ',Len);
end.

==========================

Bart

Reply by email

Bart
to Juha Manninen: On Tue, Dec 28, 2021 at 1:45 PM Bart via lazarus < I confess I didn't remember what…
PM Juha Manninen wrote 6 lines
I confess I didn't remember what PopCnt does. I checked from the net. FPC implements it as internproc. function PopCnt(Const AValue : QWord): QWord;[internproc:fpc_in_popcnt_x]; I guess it translates to one x86_64 instruction. Is it implemented for all CPUs? I found this: https://gitlab.com/freepascal.org/fpc/source/-/issues/38729

I just tested PopCnt vs Multiplication on win32 and win64. The version with PopCnt is appr. 3 times slower on both 32 and 64 bit!

C:\Users\Bart\LazarusProjecten\bugs\Utf8\ulenfast>fpc ulen.lpr Free Pascal Compiler version 3.3.1 [2021/12/08] for i386 Copyright (c) 1993-2021 by Florian Klaempfl and others Target OS: Win32 for i386

C:\Users\Bart\LazarusProjecten\bugs\Utf8\ulenfast>ulen Unsigned version with multiplication: 1344
Unsigned version with PopCnt : 3563

C:\Users\Bart\LazarusProjecten\bugs\Utf8\ulenfast>fpc ulen.lpr -Px86_64 Free Pascal Compiler version 3.3.1 [2021/12/08] for x86_64 Copyright (c) 1993-2021 by Florian Klaempfl and others Target OS: Win64 for x64

C:\Users\Bart\LazarusProjecten\bugs\Utf8\ulenfast>ulen Unsigned version with multiplication: 656
Unsigned version with PopCnt : 3797

It looks like PopCnt on these platforms at least calls the generic version (/rtl/inc/generic.inc).

Bart

Reply by email

Bart
to Marco van de Voort
PM Marco van de Voort wrote
You need an appropriate minimal CPU with -Cp Try e.g. -Cpcoreavx for core 3000 series and higher

Thanks for that.

Up to PENTIUMM: PopCnt slower
COREI : approximately equally fast
COREAVX PopCnt slightly faster
COREAVX2 PopCnt slightly faster

Most likely not worth bothering.
In code can we check (at compile time) for which instructionset the code was compiled?

Bart

Reply by email

Florian Klämpfl
to Bart
Bart wrote 20 lines
On Tue, Dec 28, 2021 at 2:46 PM Marco van de Voort via lazarus wrote: > You need an appropriate minimal CPU with -Cp > > > Try e.g. -Cpcoreavx for core 3000 series and higher Thanks for that. Up to PENTIUMM: PopCnt slower COREI : approximately equally fast COREAVX PopCnt slightly faster COREAVX2 PopCnt slightly faster Most likely not worth bothering. In code can we check (at compile time) for which instructionset the code was compiled?

For X86, check for the define CPUX86_HAS_POPCNT (compile time!).

Reply by email

Marco van de Voort
to Bart: On Tue, Dec 28, 2021 at 2:46 PM Marco van de Voort via lazarus Thanks for that. Up to…

Op 12/28/2021 om 3:01 PM schreef Bart via lazarus:

Show quoted text 7 lines
-Cpcoreavx for core 3000 series and higher Thanks for that. Up to PENTIUMM: PopCnt slower COREI : approximately equally fast COREAVX PopCnt slightly faster COREAVX2 PopCnt slightly faster

On what machine did you test? The settings if for the generated code, but the actual processor determines the effective speed.

Most likely not worth bothering. In code can we check (at compile time) for which instructionset the code was compiled?

Not that I know. Moreover with AMD etc there would be no simple "x or greater"? kind of criterium other than having popcnt

Reply by email

Bart
to Marco van de Voort
PM Marco van de Voort wrote
On what machine did you test? The settings if for the generated code, but the actual processor determines the effective speed.

I have a Intel i5 7th generation on my Win10-64 laptop from approx. 2017 (so, it's really old for more modern folks than me).

Compiled for 32-bit:
With -CpCOREI
Unsigned version with multiplication: 1359
Unsigned version with PopCnt : 1282

Compiled for 32-bit:
With -CpCOREAVX2
Unsigned version with multiplication: 1312
Unsigned version with PopCnt : 1297

Compiled for 32-bit
No -Cp switch
Unsigned version with multiplication: 1329
Unsigned version with PopCnt : 3546

B.t.w. what happens if I compile for e.g. CoreAVX2 but my processor does not support that instructionset.
Will the compilation/build fail, or will the executable just error out?

Bart

Reply by email

Florian Klämpfl
to Bart
Bart wrote 28 lines
On Tue, Dec 28, 2021 at 3:39 PM Marco van de Voort via lazarus wrote: > On what machine did you test? The settings if for the generated code, > but the actual processor determines the effective speed. I have a Intel i5 7th generation on my Win10-64 laptop from approx. 2017 (so, it's really old for more modern folks than me). Compiled for 32-bit: With -CpCOREI Unsigned version with multiplication: 1359 Unsigned version with PopCnt : 1282 Compiled for 32-bit: With -CpCOREAVX2 Unsigned version with multiplication: 1312 Unsigned version with PopCnt : 1297 Compiled for 32-bit No -Cp switch Unsigned version with multiplication: 1329 Unsigned version with PopCnt : 3546 B.t.w. what happens if I compile for e.g. CoreAVX2 but my processor does not support that instructionset. Will the compilation/build fail, or will the executable just error out?

Crash at run time with sigill. Popcnt was introduced with Nehalem, so >10 years ago.

Reply by email

Marc Weustink
to Noel Duffy: Oh no, Fedora runs on an Intel x86_64. I don't think there's a Fedora that runs on…
Noel Duffy wrote 18 lines
On 28/12/21 01:47, Juha Manninen via lazarus wrote: > On Mon, Dec 27, 2021 at 1:44 AM Noel Duffy via lazarus < > > wrote: > >> I need some help getting to the root of a problem with incorrect results >> on Apple hardware (M1, aarch64) for the function UTF8LengthFast in >> lazutf8. >> >> On MacOS, when given a string containing one or more UTF8 characters, >> UTF8LengthFast returns wildly incorrect results. On Fedora, the function >> returns the correct answer. >> > > You mean both MacOS and Fedora run on the same aarch64 CPU? Oh no, Fedora runs on an Intel x86_64. I don't think there's a Fedora that runs on aarch64. I included the results from Fedora just to show that the code does work in some places.

I've tried on pine64 aarch64 linux little-endian fpc 3.2.0 and it showed the correct results using your example code in another mail

Marc

Show quoted text 12 lines
> It must be a Big endian / Little endian issue. IIRC it can be adjusted in > ARM CPUs. > Why do MacOS and Linux use a different setting there? I have no idea. Well, as Florian said above, the M1 is little-endian. So it doesn't appear to be an endian issue. https://developer.apple.com/documentation/apple-silicon/porting-your-macos-apps-to-apple-silicon

Reply by email

Noel Duffy
to Bart: On Tue, Dec 28, 2021 at 12:08 PM Martin Frb via lazarus @Noel: Here's example code…
Bart wrote
fpc -al ulen.pas This will produce the file ulen.s You can attach or copy that here.

File is attached.

The output from running this program is:

% ./ulen
Signed version
Len = -100663283
Unsigned version
Len = 1

To add another wrinkle to this, the signed and unsigned versions of the code both work if compiled with fpc 3.2.2. It's only when compiled with 3.3.1 that I get an incorrect result.
-------------- next part --------------
# Begin asmlist al_procedures

.text
.align 4
.globl _P$ULEN_$$_UTF8LENGTHFAST_SIGNED$PCHAR$INT64$$INT64 _P$ULEN_$$_UTF8LENGTHFAST_SIGNED$PCHAR$INT64$$INT64: # [ulen.pas]
# [28] begin
stp x29,x30,[sp, #-16]!
mov x29,sp
sub sp,sp,#64
# Var p located at sp+0, size=OS_64
# Var ByteCount located at sp+8, size=OS_S64
# Var $result located at sp+16, size=OS_S64
# Var nx located at sp+24, size=OS_S64
# Var i located at sp+32, size=OS_S64
# Var cnt located at sp+40, size=OS_S64
# Var e located at sp+48, size=OS_S64
str x0,[sp]
str x1,[sp, #8]
# [29] Result := 0;
str xzr,[sp, #16]
# [30] e := ix+ByteCount; // End marker
ldr x0,[sp]
ldr x1,[sp, #8]
add x0,x1,x0
str x0,[sp, #48]
# [32] cnt := (not (ix-1)) and (sizeof(PtrInt)-1); ldr x0,[sp]
sub x0,x0,#1
mvn x0,x0
and x0,x0,#7
str x0,[sp, #40]
# [33] if cnt>ByteCount then
ldr x0,[sp, #40]
ldr x1,[sp, #8]
cmp x0,x1
b.gt Lj7
b Lj8
Lj7:
# [34] cnt := ByteCount;
ldr x0,[sp, #8]
str x0,[sp, #40]
Lj8:
# [35] for i := 1 to cnt do
ldr x1,[sp, #40]
cmp x1,#1
b.ge Lj9
b Lj10
Lj9:
str xzr,[sp, #32]
Lj11:
ldr x0,[sp, #32]
add x0,x0,#1
str x0,[sp, #32]
# [43] Result += (pn8^ shr 7) and ((not pn8^) shr 6); ldr x0,[sp]
ldrsb w0,[x0]
eor w0,w0,#255
lsr w0,w0,#6
ldr x2,[sp]
ldrsb w2,[x2]
lsr w2,w2,#7
and w0,w2,w0
sxtw x0,w0
ldr x2,[sp, #16]
add x0,x2,x0
str x0,[sp, #16]
# [44] inc(pn8);
ldr x0,[sp]
add x0,x0,#1
str x0,[sp]
ldr x0,[sp, #32]
cmp x1,x0
b.le Lj13
b Lj11
Lj13:
Lj10:
# [47] for i := 1 to (ByteCount-cnt) div sizeof(PtrUInt) do ldr x0,[sp, #8]
ldr x1,[sp, #40]
sub x0,x0,x1
asr x1,x0,#63
add x0,x0,x1,lsr #61
asr x0,x0,#3
cmp x0,#1
b.ge Lj14
b Lj15
Lj14:
str xzr,[sp, #32]
Lj16:
ldr x1,[sp, #32]
add x1,x1,#1
str x1,[sp, #32]
# [50] nx := ((pnx^ and EIGHTYMASK) shr 7) and ((not pnx^) shr 6); ldr x1,[sp]
ldr x1,[x1]
and x1,x1,#0x8080808080808080
lsr x1,x1,#7
ldr x2,[sp]
ldr x2,[x2]
mvn x2,x2
lsr x2,x2,#6
and x1,x2,x1
str x1,[sp, #24]
# [52] Result += (nx * ONEMASK) >> ((sizeof(PtrInt) - 1) * 8); ldr x1,[sp, #24]
mov x2,#72340172838076673
mul x1,x1,x2
lsr x1,x1,#56
ldr x2,[sp, #16]
add x1,x2,x1
str x1,[sp, #16]
# [54] inc(pnx);
ldr x1,[sp]
add x1,x1,#8
str x1,[sp]
ldr x1,[sp, #32]
cmp x0,x1
b.le Lj18
b Lj16
Lj18:
Lj15:
# [57] while ix<e do
b Lj20
Lj19:
# [66] Result += (pn8^ shr 7) and ((not pn8^) shr 6); ldr x0,[sp]
ldrsb w0,[x0]
eor w0,w0,#255
lsr w0,w0,#6
ldr x1,[sp]
ldrsb w1,[x1]
lsr w1,w1,#7
and w0,w1,w0
sxtw x1,w0
ldr x0,[sp, #16]
add x0,x0,x1
str x0,[sp, #16]
# [67] inc(pn8);
ldr x0,[sp]
add x0,x0,#1
str x0,[sp]
Lj20:
ldr x0,[sp]
ldr x1,[sp, #48]
cmp x0,x1
b.lt Lj19
b Lj21
Lj21:
# [69] Result := ByteCount - Result;
ldr x1,[sp, #8]
ldr x0,[sp, #16]
sub x0,x1,x0
str x0,[sp, #16]
# [70] end;
ldr x0,[sp, #16]
mov sp,x29
ldp x29,x30,[sp], #16
ret

.text
.align 4
.globl _P$ULEN_$$_UTF8LENGTHFAST_UNSIGNED$PCHAR$INT64$$INT64 _P$ULEN_$$_UTF8LENGTHFAST_UNSIGNED$PCHAR$INT64$$INT64: # [88] begin
stp x29,x30,[sp, #-16]!
mov x29,sp
sub sp,sp,#64
# Var p located at sp+0, size=OS_64
# Var ByteCount located at sp+8, size=OS_S64
# Var $result located at sp+16, size=OS_S64
# Var nx located at sp+24, size=OS_64
# Var i located at sp+32, size=OS_S64
# Var cnt located at sp+40, size=OS_S64
# Var e located at sp+48, size=OS_S64
str x0,[sp]
str x1,[sp, #8]
# [89] Result := 0;
str xzr,[sp, #16]
# [90] e := ix+PtrUInt(ByteCount); // End marker
ldr x1,[sp]
ldr x0,[sp, #8]
add x0,x0,x1
str x0,[sp, #48]
# [92] cnt := (not (ix-1)) and (sizeof(PtrInt)-1); ldr x0,[sp]
sub x0,x0,#1
mvn x0,x0
and x0,x0,#7
str x0,[sp, #40]
# [93] if cnt>ByteCount then
ldr x0,[sp, #40]
ldr x1,[sp, #8]
cmp x0,x1
b.gt Lj24
b Lj25
Lj24:
# [94] cnt := ByteCount;
ldr x0,[sp, #8]
str x0,[sp, #40]
Lj25:
# [95] for i := 1 to cnt do
ldr x1,[sp, #40]
cmp x1,#1
b.ge Lj26
b Lj27
Lj26:
str xzr,[sp, #32]
Lj28:
ldr x0,[sp, #32]
add x0,x0,#1
str x0,[sp, #32]
# [103] Result += (pn8^ shr 7) and ((not pn8^) shr 6); ldr x0,[sp]
ldrb w0,[x0]
eor w0,w0,#255
lsr w0,w0,#6
ldr x2,[sp]
ldrb w2,[x2]
lsr w2,w2,#7
and w0,w2,w0
ldr x2,[sp, #16]
add x0,x2,x0
str x0,[sp, #16]
# [104] inc(pn8);
ldr x0,[sp]
add x0,x0,#1
str x0,[sp]
ldr x0,[sp, #32]
cmp x1,x0
b.le Lj30
b Lj28
Lj30:
Lj27:
# [107] for i := 1 to (ByteCount-cnt) div sizeof(PtrUInt) do ldr x0,[sp, #8]
ldr x1,[sp, #40]
sub x0,x0,x1
asr x1,x0,#63
add x0,x0,x1,lsr #61
asr x0,x0,#3
cmp x0,#1
b.ge Lj31
b Lj32
Lj31:
str xzr,[sp, #32]
Lj33:
ldr x1,[sp, #32]
add x1,x1,#1
str x1,[sp, #32]
# [110] nx := ((pnx^ and EIGHTYMASK) shr 7) and ((not pnx^) shr 6); ldr x1,[sp]
ldr x1,[x1]
and x1,x1,#0x8080808080808080
lsr x1,x1,#7
ldr x2,[sp]
ldr x2,[x2]
mvn x2,x2
lsr x2,x2,#6
and x1,x2,x1
str x1,[sp, #24]
# [112] Result += (nx * ONEMASK) >> ((sizeof(PtrInt) - 1) * 8); ldr x1,[sp, #24]
mov x2,#72340172838076673
mul x1,x1,x2
lsr x1,x1,#56
ldr x2,[sp, #16]
add x1,x2,x1
str x1,[sp, #16]
# [114] inc(pnx);
ldr x1,[sp]
add x1,x1,#8
str x1,[sp]
ldr x1,[sp, #32]
cmp x0,x1
b.le Lj35
b Lj33
Lj35:
Lj32:
# [117] while ix<e do
b Lj37
Lj36:
# [126] Result += (pn8^ shr 7) and ((not pn8^) shr 6); ldr x0,[sp]
ldrb w0,[x0]
eor w0,w0,#255
lsr w0,w0,#6
ldr x1,[sp]
ldrb w1,[x1]
lsr w1,w1,#7
and w0,w1,w0
ldr x1,[sp, #16]
add x0,x1,x0
str x0,[sp, #16]
# [127] inc(pn8);
ldr x0,[sp]
add x0,x0,#1
str x0,[sp]
Lj37:
ldr x0,[sp]
ldr x1,[sp, #48]
cmp x0,x1
b.lt Lj36
b Lj38
Lj38:
# [129] Result := ByteCount - Result;
ldr x0,[sp, #8]
ldr x1,[sp, #16]
sub x0,x0,x1
str x0,[sp, #16]
# [130] end;
ldr x0,[sp, #16]
mov sp,x29
ldp x29,x30,[sp], #16
ret

.text
.align 4
.globl _main
_main:
stp x29,x30,[sp, #-16]!
mov x29,sp
sub sp,sp,#32
# Var ARGC located at sp+0, size=OS_S32
# Var ARGV located at sp+8, size=OS_64
# Var ARGP located at sp+16, size=OS_64
str w0,[sp]
str x1,[sp, #8]
str x2,[sp, #16]
ldr x2,[sp, #16]
ldr x1,[sp, #8]
ldr w0,[sp]
bl _FPC_SYSTEMMAIN
mov sp,x29
ldp x29,x30,[sp], #16
ret

.text
.align 4
.globl _PASCALMAIN
_PASCALMAIN:
# [ulen.pas]
# [136] begin
stp x29,x30,[sp, #-16]!
mov x29,sp
stp x19,x19,[sp, #-16]!
bl fpc_initializeunits
# [137] DefaultSystemCodePage := CP_UTF8;
movz w0,#65001
adrp x1,_U_$SYSTEM_$$_DEFAULTSYSTEMCODEPAGE at GOTPAGE ldr x1,[x1, _U_$SYSTEM_$$_DEFAULTSYSTEMCODEPAGE at GOTPAGEOFF] sturh w0,[x1]
# [138] Euro := '...';
adrp x1,_$$fpclocal$_ld1 at PAGE
add x1,x1,_$$fpclocal$_ld1 at PAGEOFF
add x1,x1,#16
adrp x0,_U_$P$ULEN_$$_EURO at GOTPAGE
ldr x0,[x0, _U_$P$ULEN_$$_EURO at GOTPAGEOFF]
bl fpc_ansistr_assign
# [140] writeln('Signed version');
bl fpc_get_output
mov x19,x0
adrp x2,_$ULEN$_Ld2 at GOTPAGE
ldr x2,[x2, _$ULEN$_Ld2 at GOTPAGEOFF]
mov x1,x19
movz w0,#0
bl fpc_write_text_shortstr
bl fpc_iocheck
mov x0,x19
bl fpc_writeln_end
bl fpc_iocheck
# [141] Len := Utf8LengthFast_Signed(PChar(Euro), Length(Euro)); adrp x0,_U_$P$ULEN_$$_EURO at GOTPAGE
ldr x0,[x0, _U_$P$ULEN_$$_EURO at GOTPAGEOFF]
ldur x1,[x0]
cmp x1,#0
b.eq Lj45
ldur x1,[x1, #-8]
Lj45:
adrp x0,_U_$P$ULEN_$$_EURO at GOTPAGE
ldr x0,[x0, _U_$P$ULEN_$$_EURO at GOTPAGEOFF]
ldur x0,[x0]
cmp x0,#0
b.ne Lj46
adrp x0,FPC_EMPTYCHAR at GOTPAGE
ldr x0,[x0, FPC_EMPTYCHAR at GOTPAGEOFF]
Lj46:
bl _P$ULEN_$$_UTF8LENGTHFAST_SIGNED$PCHAR$INT64$$INT64 adrp x1,_U_$P$ULEN_$$_LEN at GOTPAGE
ldr x1,[x1, _U_$P$ULEN_$$_LEN at GOTPAGEOFF]
stur x0,[x1]
# [142] writeln('Len = ',Len);
bl fpc_get_output
mov x19,x0
adrp x2,_$ULEN$_Ld3 at GOTPAGE
ldr x2,[x2, _$ULEN$_Ld3 at GOTPAGEOFF]
mov x1,x19
movz w0,#0
bl fpc_write_text_shortstr
bl fpc_iocheck
adrp x0,_U_$P$ULEN_$$_LEN at GOTPAGE
ldr x0,[x0, _U_$P$ULEN_$$_LEN at GOTPAGEOFF]
ldur x2,[x0]
mov x1,x19
movz w0,#0
bl fpc_write_text_sint
bl fpc_iocheck
mov x0,x19
bl fpc_writeln_end
bl fpc_iocheck
# [144] writeln('Unsigned version');
bl fpc_get_output
mov x19,x0
adrp x2,_$ULEN$_Ld4 at GOTPAGE
ldr x2,[x2, _$ULEN$_Ld4 at GOTPAGEOFF]
mov x1,x19
movz w0,#0
bl fpc_write_text_shortstr
bl fpc_iocheck
mov x0,x19
bl fpc_writeln_end
bl fpc_iocheck
# [145] Len := Utf8LengthFast_Unsigned(PChar(Euro), Length(Euro)); adrp x0,_U_$P$ULEN_$$_EURO at GOTPAGE
ldr x0,[x0, _U_$P$ULEN_$$_EURO at GOTPAGEOFF]
ldur x1,[x0]
cmp x1,#0
b.eq Lj47
ldur x1,[x1, #-8]
Lj47:
adrp x0,_U_$P$ULEN_$$_EURO at GOTPAGE
ldr x0,[x0, _U_$P$ULEN_$$_EURO at GOTPAGEOFF]
ldur x0,[x0]
cmp x0,#0
b.ne Lj48
adrp x0,FPC_EMPTYCHAR at GOTPAGE
ldr x0,[x0, FPC_EMPTYCHAR at GOTPAGEOFF]
Lj48:
bl _P$ULEN_$$_UTF8LENGTHFAST_UNSIGNED$PCHAR$INT64$$INT64 adrp x1,_U_$P$ULEN_$$_LEN at GOTPAGE
ldr x1,[x1, _U_$P$ULEN_$$_LEN at GOTPAGEOFF]
stur x0,[x1]
# [146] writeln('Len = ',Len);
bl fpc_get_output
mov x19,x0
adrp x2,_$ULEN$_Ld3 at GOTPAGE
ldr x2,[x2, _$ULEN$_Ld3 at GOTPAGEOFF]
mov x1,x19
movz w0,#0
bl fpc_write_text_shortstr
bl fpc_iocheck
adrp x0,_U_$P$ULEN_$$_LEN at GOTPAGE
ldr x0,[x0, _U_$P$ULEN_$$_LEN at GOTPAGEOFF]
ldur x2,[x0]
mov x1,x19
movz w0,#0
bl fpc_write_text_sint
bl fpc_iocheck
mov x0,x19
bl fpc_writeln_end
bl fpc_iocheck
# [147] end.
bl fpc_do_exit
ret

.text
.align 4
.globl __P$ULEN_$$_init_implicit$
__P$ULEN_$$_init_implicit$:
.set _INIT$_$P$ULEN, __P$ULEN_$$_init_implicit$
.globl _INIT$_$P$ULEN
stp x29,x30,[sp, #-16]!
mov x29,sp
ldp x29,x30,[sp], #16
ret

.text
.align 4
.globl __P$ULEN_$$_finalize_implicit$
__P$ULEN_$$_finalize_implicit$:
.set _FINALIZE$_$P$ULEN, __P$ULEN_$$_finalize_implicit$ .globl _FINALIZE$_$P$ULEN
.set PASCALFINALIZE, __P$ULEN_$$_finalize_implicit$ .globl PASCALFINALIZE
stp x29,x30,[sp, #-16]!
mov x29,sp
adrp x0,_U_$P$ULEN_$$_EURO at GOTPAGE
ldr x0,[x0, _U_$P$ULEN_$$_EURO at GOTPAGEOFF]
bl fpc_ansistr_decr_ref
ldp x29,x30,[sp], #16
ret
# End asmlist al_procedures
# Begin asmlist al_globals

.align 3
# [ulen.pas]
# [134] Len: PtrInt;
.private_extern _U_$P$ULEN_$$_LEN
.globl _U_$P$ULEN_$$_LEN
.data
.zerofill __DATA, __common, _U_$P$ULEN_$$_LEN, 8,3

.align 3
# [135] Euro: String;
.private_extern _U_$P$ULEN_$$_EURO
.globl _U_$P$ULEN_$$_EURO
.data
.zerofill __DATA, __common, _U_$P$ULEN_$$_EURO, 8,3

.data
.align 3
.globl INITFINAL
INITFINAL:
.quad 5,0
.quad _INIT$_$SYSTEM
.quad 0,0
.quad _FINALIZE$_$OBJPAS
.quad _INIT$_$UNIX
.quad _FINALIZE$_$UNIX
.quad _INIT$_$SYSUTILS
.quad _FINALIZE$_$SYSUTILS
.quad _INIT$_$P$ULEN
.quad _FINALIZE$_$P$ULEN

.data
.align 3
.globl FPC_THREADVARTABLES
FPC_THREADVARTABLES:
.long 1
.byte 0,0,0,0
.quad _THREADVARLIST_$SYSTEM$indirect

.const_data
.align 3
.globl FPC_RESOURCESTRINGTABLES
FPC_RESOURCESTRINGTABLES:
.quad 1
.quad _RESSTR_$SYSCONST_$$_START$indirect
.quad _RESSTR_$SYSCONST_$$_END$indirect

.data
.align 3
.globl FPC_WIDEINITTABLES
FPC_WIDEINITTABLES:
.quad 0

.data
.align 3
.globl FPC_RESSTRINITTABLES
FPC_RESSTRINITTABLES:
.quad 0

.section __TEXT, .fpc, regular, no_dead_strip
.align 4
.reference __fpc_ident
__fpc_ident:
.ascii "FPC 3.3.1 [2021/11/12] for aarch64 - Darwin"

.data
.align 3
.globl __stklen
__stklen:
.quad 8388608

.data
.align 3
.globl __heapsize
__heapsize:
.quad 0

.data
.align 3
.globl __fpc_valgrind
__fpc_valgrind:
.byte 0

.const_data
.align 3
.globl FPC_RESLOCATION
FPC_RESLOCATION:
.quad 0
# End asmlist al_globals
# Begin asmlist al_typedconsts

.const
.align 3
_$$fpclocal$_ld1:
.short 65001,1
.long -1
.quad 3
.ascii "\342\202\254\000"

.const
.align 3
.globl _$ULEN$_Ld2
_$ULEN$_Ld2:
.ascii "\016Signed version\000"

.const
.align 3
.globl _$ULEN$_Ld3
_$ULEN$_Ld3:
.ascii "\006Len = \000"

.const
.align 3
.globl _$ULEN$_Ld4
_$ULEN$_Ld4:
.ascii "\020Unsigned version\000"
# End asmlist al_typedconsts
# Begin asmlist al_rtti

.const_data
.align 3
.globl _RTTI_$P$ULEN_$$_def00000008
_RTTI_$P$ULEN_$$_def00000008:
.byte 9,0,0,0,0,0,0,0
.quad 0
.short 65001
.byte 0,0,0,0,0,0
# End asmlist al_rtti
# Begin asmlist al_indirectglobals

.const_data
.align 3
.globl _RTTI_$P$ULEN_$$_def00000008$indirect
_RTTI_$P$ULEN_$$_def00000008$indirect:
.quad _RTTI_$P$ULEN_$$_def00000008
# End asmlist al_indirectglobals
.subsections_via_symbols

Reply by email

Martin Frb
to Noel Duffy
Noel Duffy wrote 7 lines
On 29/12/21 01:26, Bart via lazarus wrote: > fpc -al ulen.pas > This will produce the file ulen.s > You can attach or copy that here. File is attached.

Thanks.
And I think there is a bug in FPC

This is the signed version

# [43] Result += (pn8^ shr 7) and ((not pn8^) shr 6); ?? ?ldr??? x0,[sp]
?? ?ldrsb??? w0,[x0]???????? # <<<<< sign extend to a 32bit value (32bit register).
?? ?eor??? w0,w0,#255?? # <<<<< But only "not" the lowest 8 bit. That is wrong. The calculation uses 32 bit at this point ?? ?lsr??? w0,w0,#6
?? ?ldr??? x2,[sp]
?? ?ldrsb??? w2,[x2]
?? ?lsr??? w2,w2,#7
?? ?and??? w0,w2,w0??? # <<<<<< here the full 32 bit are used.

Had the full 32 bits been "not"ed, the the upper 24 bit where 0 (because they had been sign extended to 1).
This would mask all the 1, that were sign extended in w2.

Had the char been < 128 (high bit = 0), then w0 would have the upper 24 bit = 1 / but w2 would have them 0.

And that is why the code worked, even with signed values. (signed values were still a bad idea)

Reply by email

Noel Duffy
to Martin Frb
Martin Frb wrote 37 lines
On 28/12/2021 22:05, Noel Duffy via lazarus wrote: > On 29/12/21 01:26, Bart via lazarus wrote: >> fpc -al ulen.pas > > > This will produce the file ulen.s > > You can attach or copy that here. > > File is attached. Thanks. And I think there is a bug in FPC This is the signed version # [43] Result += (pn8^ shr 7) and ((not pn8^) shr 6); ?? ?ldr??? x0,[sp] ?? ?ldrsb??? w0,[x0]???????? # <<<<< sign extend to a 32bit value (32bit register). ?? ?eor??? w0,w0,#255?? # <<<<< But only "not" the lowest 8 bit. That is wrong. The calculation uses 32 bit at this point ?? ?lsr??? w0,w0,#6 ?? ?ldr??? x2,[sp] ?? ?ldrsb??? w2,[x2] ?? ?lsr??? w2,w2,#7 ?? ?and??? w0,w2,w0??? # <<<<<< here the full 32 bit are used. Had the full 32 bits been "not"ed, the the upper 24 bit where 0 (because they had been sign extended to 1). This would mask all the 1, that were sign extended in w2. Had the char been < 128 (high bit = 0), then w0 would have the upper 24 bit = 1 / but w2 would have them 0. And that is why the code worked, even with signed values. (signed values were still a bad idea)

Interesting. As I noted in my message with the ulen.s attached, I tested and found the problem is not present in fpc 3.2.2. That's the version of fpc packaged by Lazarus, and I used that to compile fpc 3.3.1.

The assembler produced by 3.2.2 looks like this:

# [43] Result += (pn8^ shr 7) and ((not pn8^) shr 6); ldr x0,[sp]
ldrsb w0,[x0]
mvn w0,w0
sxtb w0,w0
lsr w1,w0,#6
ldr x0,[sp]
ldrsb w0,[x0]
lsr w0,w0,#7
and w0,w0,w1
sxtw x0,w0
ldr x1,[sp, #16]
add x0,x1,x0
str x0,[sp, #16]

Reply by email

Martin Frb
to Bart: On Tue, Dec 28, 2021 at 3:39 PM Marco van de Voort via lazarus I have a Intel i5 7th…
Bart wrote 13 lines
On Tue, Dec 28, 2021 at 3:39 PM Marco van de Voort via lazarus wrote: > On what machine did you test? The settings if for the generated code, > but the actual processor determines the effective speed. I have a Intel i5 7th generation on my Win10-64 laptop from approx. 2017 (so, it's really old for more modern folks than me). Compiled for 32-bit: With -CpCOREI Unsigned version with multiplication: 1359 Unsigned version with PopCnt : 1282

I have a core I7-8600
The diff between the old code and popcnt is less significant.

old: 715
pop: 695

But there is a 3rd way, that is faster.
add: 610

"nx" has a single "1" in each of the 8 bytes in a Qword (based on 64bit). If we regard each of this bytes as an entity of its own, then we can keep adding those "1".

We could add the 1 of up to 255 iteration, before an overflow can happen. The example only does 128, as this avoids the "div" and "mod" operations.

The full routine / incl benchmark for all 3 versions is attached

For 64 bit:

 ? bc := (ByteCount-cnt) div sizeof(PtrInt);
 ? for j := 1 to bc >> 7 do begin
 ??? nx := 0;
 ??? for i := 0 to 127 do
 ??? begin
 ????? // Count bytes which are NOT the first byte of a character.
 ????? nx += ((pnx^ and EIGHTYMASK) shr 7) and ((not pnx^) shr 6);
 ????? inc(pnx);
 ??? end;
 ??? nx := (nx and $00FF00FF00FF00FF) + ((nx >>? 8) and $00FF00FF00FF00FF);
 ??? nx := (nx and $0000FFFF0000FFFF) + ((nx >> 16) and $0000FFFF0000FFFF);
 ??? nx := (nx and $00000000FFFFFFFF) + ((nx >> 32) and $00000000FFFFFFFF);
 ??? Result := Result + nx;
 ? end;
 ? if (bc and 127) > 0 then begin
 ? nx := 0;
 ? for i := 1 to bc and 127 do
 ? begin
 ??? // Count bytes which are NOT the first byte of a character.
 ??? nx += ((pnx^ and EIGHTYMASK) shr 7) and ((not pnx^) shr 6);
 ??? inc(pnx);
 ? end;
 ??? nx := (nx and $00FF00FF00FF00FF) + ((nx >>? 8) and $00FF00FF00FF00FF);
 ??? nx := (nx and $0000FFFF0000FFFF) + ((nx >> 16) and $0000FFFF0000FFFF);
 ??? nx := (nx and $00000000FFFFFFFF) + ((nx >> 32) and $00000000FFFFFFFF);
 ??? Result := Result + nx;
 ? end;
-------------- next part --------------
program Project1;
{$mode objfpc}{$H+}
uses SysUtils;
function UTF8LengthFast(p: PChar; ByteCount: PtrInt): PtrInt;
const
{$ifdef CPU32}
  ONEMASK   =$01010101;
  EIGHTYMASK=$80808080;
{$endif}
{$ifdef CPU64}
  ONEMASK   =$0101010101010101;
  EIGHTYMASK=$8080808080808080;
{$endif}
var
  pnx: PPtrInt absolute p; // To get contents of text in PtrInt blocks. x refers to 32 or 64 bits
  pn8: pint8 absolute pnx; // To read text as Int8 in the initial and final loops
  ix: PtrInt absolute pnx; // To read text as PtrInt in the block loop
  nx: PtrInt;              // values processed in block loop
  i,cnt,e: PtrInt;
begin
  Result := 0;
  e := ix+ByteCount; // End marker
  // Handle any initial misaligned bytes.
  cnt := (not (ix-1)) and (sizeof(PtrInt)-1);
  if cnt>ByteCount then
    cnt := ByteCount;
  for i := 1 to cnt do
  begin
    // Is this byte NOT the first byte of a character?
    Result += (pn8^ shr 7) and ((not pn8^) shr 6);
    inc(pn8);
  end;
  // Handle complete blocks
  for i := 1 to (ByteCount-cnt) div sizeof(PtrInt) do
  begin
    // Count bytes which are NOT the first byte of a character.
    nx := ((pnx^ and EIGHTYMASK) shr 7) and ((not pnx^) shr 6);
    {$push}{$overflowchecks off} // "nx * ONEMASK" causes an arithmetic overflow.
    Result += (nx * ONEMASK) >> ((sizeof(PtrInt) - 1) * 8);
    {$pop}
    inc(pnx);
  end;
  // Take care of any left-over bytes.
  while ix<e do
  begin
    // Is this byte NOT the first byte of a character?
    Result += (pn8^ shr 7) and ((not pn8^) shr 6);
    inc(pn8);
  end;
  Result := ByteCount - Result;
end;
function UTF8LengthPop(p: PChar; ByteCount: PtrInt): PtrInt;
const
{$ifdef CPU32}
  ONEMASK   =$01010101;
  EIGHTYMASK=$80808080;
{$endif}
{$ifdef CPU64}
  ONEMASK   =$0101010101010101;
  EIGHTYMASK=$8080808080808080;
{$endif}
var
  pnx: PPtrInt absolute p; // To get contents of text in PtrInt blocks. x refers to 32 or 64 bits
  pn8: pint8 absolute pnx; // To read text as Int8 in the initial and final loops
  ix: PtrInt absolute pnx; // To read text as PtrInt in the block loop
  nx: PtrInt;              // values processed in block loop
  i,cnt,e: PtrInt;
begin
  Result := 0;
  e := ix+ByteCount; // End marker
  // Handle any initial misaligned bytes.
  cnt := (not (ix-1)) and (sizeof(PtrInt)-1);
  if cnt>ByteCount then
    cnt := ByteCount;
  for i := 1 to cnt do
  begin
    // Is this byte NOT the first byte of a character?
    Result += (pn8^ shr 7) and ((not pn8^) shr 6);
    inc(pn8);
  end;
  // Handle complete blocks
  for i := 1 to (ByteCount-cnt) div sizeof(PtrInt) do
  begin
    // Count bytes which are NOT the first byte of a character.
    nx := ((pnx^ and EIGHTYMASK) shr 7) and ((not pnx^) shr 6);
    {$push}{$overflowchecks off} // "nx * ONEMASK" causes an arithmetic overflow.
    //Result += (nx * ONEMASK) >> ((sizeof(PtrInt) - 1) * 8);
    Result += PopCnt(qword(nx));
    {$pop}
    inc(pnx);
  end;
  // Take care of any left-over bytes.
  while ix<e do
  begin
    // Is this byte NOT the first byte of a character?
    Result += (pn8^ shr 7) and ((not pn8^) shr 6);
    inc(pn8);
  end;
  Result := ByteCount - Result;
end;
function UTF8LengthAdd(p: PChar; ByteCount: PtrInt): PtrInt;
const
{$ifdef CPU32}
  ONEMASK   =$01010101;
  EIGHTYMASK=$80808080;
{$endif}
{$ifdef CPU64}
  ONEMASK   =$0101010101010101;
  EIGHTYMASK=$8080808080808080;
{$endif}
var
  pnx: PPtrInt absolute p; // To get contents of text in PtrInt blocks. x refers to 32 or 64 bits
  pn8: pint8 absolute pnx; // To read text as Int8 in the initial and final loops
  ix: PtrInt absolute pnx; // To read text as PtrInt in the block loop
  nx: PtrInt;              // values processed in block loop
  i,j,cnt,e, bc: PtrInt;
begin
  Result := 0;
  e := ix+ByteCount; // End marker
  // Handle any initial misaligned bytes.
  cnt := (not (ix-1)) and (sizeof(PtrInt)-1);
  if cnt>ByteCount then
    cnt := ByteCount;
  for i := 1 to cnt do
  begin
    // Is this byte NOT the first byte of a character?
    Result += (pn8^ shr 7) and ((not pn8^) shr 6);
    inc(pn8);
  end;
  // Handle complete blocks
  bc := (ByteCount-cnt) div sizeof(PtrInt);
  for j := 1 to bc >> 7 do begin
    nx := 0;
    for i := 0 to 127 do
    begin
      // Count bytes which are NOT the first byte of a character.
      nx += ((pnx^ and EIGHTYMASK) shr 7) and ((not pnx^) shr 6);
      inc(pnx);
    end;
    nx := (nx and $00FF00FF00FF00FF) + ((nx >>  8) and $00FF00FF00FF00FF);
    nx := (nx and $0000FFFF0000FFFF) + ((nx >> 16) and $0000FFFF0000FFFF);
    nx := (nx and $00000000FFFFFFFF) + ((nx >> 32) and $00000000FFFFFFFF);
    Result := Result + nx;
  end;
  if (bc and 127) > 0 then begin
  nx := 0;
  for i := 1 to bc and 127 do
  begin
    // Count bytes which are NOT the first byte of a character.
    nx += ((pnx^ and EIGHTYMASK) shr 7) and ((not pnx^) shr 6);
    inc(pnx);
  end;
    nx := (nx and $00FF00FF00FF00FF) + ((nx >>  8) and $00FF00FF00FF00FF);
    nx := (nx and $0000FFFF0000FFFF) + ((nx >> 16) and $0000FFFF0000FFFF);
    nx := (nx and $00000000FFFFFFFF) + ((nx >> 32) and $00000000FFFFFFFF);
    Result := Result + nx;
  end;
  // Take care of any left-over bytes.
  while ix<e do
  begin
    // Is this byte NOT the first byte of a character?
    Result += (pn8^ shr 7) and ((not pn8^) shr 6);
    inc(pn8);
  end;
  Result := ByteCount - Result;
end;
var
  a: ansistring;
  t: QWord;
  i, j, ii: Integer;
begin
  a := '???? ???? ?? ??? ???? ???? ?????? ?????? ?????? ??????? ?????? ??? ????? ?????? ?? ??? ???? ????? ??????? ??? ????????? ?';
  a := a+a+A+a+a+A+a+a+A+a+a+A+a+a+A+a+a+A+a+a+A+a+a+A+a+a+A+a+a+A+a+a+A+a+a+A+a+a+A+a+a+A+a+a+A+a+a+A+a+a+A;
  a := a+a+A+a+a+A+a+a+A+a+a+A+a+a+A+a+a+A+a+a+A+a+a+A+a+a+A+a+a+A+a+a+A+a+a+A+a+a+A+a+a+A+a+a+A+a+a+A+a+a+A;
  a := a+a+A+a+a+A+a+a+A+a+a+A+a+a+A+a+a+A+a+a+A+a+a+A+a+a+A+a+a+A+a+a+A+a+a+A+a+a+A+a+a+A+a+a+A+a+a+A+a+a+A;
  a := a+a+A+a+a+A+a+a;
  writeln(Length(a));
  writeln(UTF8LengthFast(@a[1], Length(a)));
  writeln(UTF8LengthPop(@a[1], Length(a)));
  writeln(UTF8LengthAdd(@a[1], Length(a)));
  WriteLn();
  writeln(Length(a) div 8);
  WriteLn();
  for ii := 0 to 1 do begin
  for i := 0 to 3 do begin
    t := GetTickCount64;
    for j := 0 to 19 do
      UTF8LengthFast(@a[1], Length(a));
    t := GetTickCount64 - t;
    writeln('fst ',t);
  end;
  for i := 0 to 3 do begin
    t := GetTickCount64;
    for j := 0 to 19 do
      UTF8LengthPop(@a[1], Length(a));
    t := GetTickCount64 - t;
    writeln('pop ',t);
  end;
  for i := 0 to 3 do begin
    t := GetTickCount64;
    for j := 0 to 19 do
      UTF8LengthAdd(@a[1], Length(a));
    t := GetTickCount64 - t;
    writeln('add ',t);
  end;
  end;
  readln;
end.

Reply by email

Marco van de Voort
to Martin Frb: I have a core I7-8600 The diff between the old code and popcnt is less significant. old:…
Martin Frb wrote 5 lines
"nx" has a single "1" in each of the 8 bytes in a Qword (based on 64bit). If we regard each of this bytes as an entity of its own, then we can keep adding those "1".

I also was thinking in that direction, but more about how to optimize that loop using SSE2

Some simple masking achieves the same (an 1 for each byte that starts with %10 bits) in 5 instructions, the load inclusive.

Since 64-bit always supports SSE2, this could work:

{$mode objfpc}{$H+}
{$asmmode intel}
uses sysutils,strutils;
Type int128 = array[0..1] of int64;

const
???? mask3?????? :? array[0..15] of byte? = ( $C0,$C0,$C0,$C0, ???????????????????????????????????????????????? $C0,$C0,$C0,$C0, ???????????????????????????????????????????????? $C0,$C0,$C0,$C0, ???????????????????????????????????????????????? $C0,$C0,$C0,$C0);

????? mask4?????? :? array[0..15] of byte? = (?? $80,$80,$80,$80, ???????????????????????????????????????????????? $80,$80,$80,$80, ???????????????????????????????????????????????? $80,$80,$80,$80, ???????????????????????????????????????????????? $80,$80,$80,$80);

????? mask2?????? :? array[0..15] of byte? = ( $1,$1,$1,$1, ?? ??? ??? ??? ??? ??? ? $1,$1,$1,$1,
???????????????????????????????????????????????? $1,$1,$1,$1, ???????????????????????????????????????????????? $1,$1,$1,$1);

function utf8length(const s : pchar;var res:int128;len:integer):integer; // len is number of 16-byte counts to accumulate, max 255 I think // stores 16 bytes worth of counts in "res"
begin
?asm
? movdqu xmm1,[rip+mask3]???????? // unaligned is SSE3, doesn't work on original X86_64 clawhammer?
? movdqu xmm2,[rip+mask4]
? movdqu xmm3,[rip+mask2]
? pxor xmm4,xmm4

@lbl:
? movdqu xmm0, [rcx]
? pand? xmm0,xmm1????? // mask out top 2 bits? ($C0) ? pcmpeqb xmm0,xmm2??? // compare with $80. sets byte to 11111111 or 00000000
? pand? xmm0,xmm3????? // change to lsb (1/0) per byte only. ? paddb? xmm4,xmm0???? // add to cumulative

? add rcx,16
? dec r8
? jne @lbl

? movdqu [rdx],xmm4

end; // no volatile registers used.
end;
function countmask(nx:int64):integer;
// Martin's routine that should be replaced by some punpkl magic, but it
is too late now.
begin
 ?? nx := (nx and $00FF00FF00FF00FF) + ((nx >>? 8) and $00FF00FF00FF00FF);
 ?? nx := (nx and $0000FFFF0000FFFF) + ((nx >> 16) and $0000FFFF0000FFFF);
 ?? result := (nx and $00000000FFFFFFFF) + ((nx >> 32) and
$00000000FFFFFFFF);
end;
// one of each pattern.
const pattern : array[0..3] of char = (chr(%11001001),chr(%10001001),
chr(%00001001),chr(%01001001));
const testblocks = 5;
var s : string;
 ??? i,j,cnt : integer;
 ??? r : int128;
begin
 ? randomize;
 ? setlength(s,testblocks*16);
 ? // random string but keep a count of bytes with high value %10
 ? cnt:=0;
 ? for i:=0 to testblocks*16-1 do
 ??? begin
 ????? j:=random(4);
 ????? if j=1 then inc(cnt);
 ????? s[i+1]:=pattern[j];
 ??? end;
 ? utf8length(pchar(s),r,testblocks+1);
 ? writeln(cnt,' = ',countmask(r[0])+countmask(r[1]));
//? writeln(inttohex(r[0],16));
//? writeln(inttohex(r[1],16));
end.

Reply by email

Martin Frb
to Marco van de Voort
Marco van de Voort wrote 11 lines
On 28-12-2021 23:35, Martin Frb via lazarus wrote: > > > "nx" has a single "1" in each of the 8 bytes in a Qword (based on > 64bit). > If we regard each of this bytes as an entity of its own, then we can > keep adding those "1". I also was thinking in that direction, but more about how to optimize that loop using SSE2

good idea...

// Martin's routine that should be replaced by some punpkl magic, but it is too late now.

Why too late?

There is a place for both. My routine works fine for cpu. (soon as a 32 bit (and maybe 16 bit) variant are added).

Then for known cpu, special handling can be added.

Reply by email

Marco van de Voort
to Martin Frb
Martin Frb wrote 5 lines
> // Martin's routine that should be replaced by some punpkl magic, but > it is too late now. Why too late?

See datetime stamp.? 02:10 AM. I don't know how it is with you Lazarus devels, but we FPC devels need our beauty sleep from time to time.

So I'll be working on it some more today, possible things to do are:

-? fold countmask into the asm, and

-? maybe do another outer loop to process all bytes in one go.

-? special case for only a few bytes

- maybe align to 16-byte (easier/cheaper for old machines than all the movdqu).

- Maybe test the code on godbolt for performance

At the very least I hope the punpkl code for countmask will be more compact since it doesn't require literals.

There is a place for both. My routine works fine for cpu. (soon as a 32 bit (and maybe 16 bit) variant are added). Then for known cpu, special handling can be added.

Could you post full source if you haven't already? For a bit of benchmarking. I just wrote it from the top of my head, and I assumed 5 instructions for 16-byte would win any time, but haven't verified anything yet.

For 32-bit this can also be done, but you'd need a procedure variable to test for SSE support, and only run this for long strings (>200-500 or so)

p.s. is there a workaround for git worktree to work on the same branch? E.g. trunk for 32-bit and trunk for 64-bit ? :-)

Reply by email

Florian Klämpfl
to Marco van de Voort
Marco van de Voort wrote
p.s. is there a workaround for git worktree to work on the same branch? E.g. trunk for 32-bit and trunk for 64-bit ? :-)

No. You cannot checkout the same branch in two worktrees. But you can do the following: create a new branch in both worktrees and check them out (main_32bit and main_64bit), do in both directories: work, commit, pull, checkout main, merge the branch, push:

cd git/fpc/main1
git checkout -b main_32bit
git worktree add ../main2 -b main_64bit

<do you work in both directories>

<commit both>

cd main1
git checkout main
git pull
git merge main_32bit
git push

cd ../main2
git checkout main
git pull
git merge main_64bit
git push

Reply by email

Martin Frb
to Marco van de Voort: See datetime stamp.? 02:10 AM. I don't know how it is with you Lazarus devels, but we FPC…
Marco van de Voort wrote 10 lines
On 29-12-2021 10:16, Martin Frb via lazarus wrote: > >> // Martin's routine that should be replaced by some punpkl magic, >> but it is too late now. > > Why too late? See datetime stamp.? 02:10 AM. I don't know how it is with you Lazarus devels, but we FPC devels need our beauty sleep from time to time.

Oh, too late for the day.
I thought. Too late, that ship has sailed.

Show quoted text 5 lines
Could you post full source if you haven't already? For a bit of benchmarking. I just wrote it from the top of my head, and I assumed 5 instructions for 16-byte would win any time, but haven't verified anything yet.

I had it attached on my last mail. Attached it again here. (3rd procedure / "Utf8LengthAdd")

It is only 64bit for now. (And not cleaned up in any way).

Also changing "bc >> 7" and "bc and 127"
to "moddiv(bc, 255, full, remain)" might save a few more ms. But probably needs larger data to benchmark.

If you do work on this, feel free to integrate my code as the baseline for cpu without SSE.
Otherwise, it might be a bit until I get to it.
-------------- next part --------------
A non-text attachment was scrubbed...
Name: Project1.pas
Type: application/x-unknown-content-type
Size: 6800 bytes
Desc: not available
URL: <http://lists.lazarus-ide.org/pipermail/lazarus/attachments/20211229/62731496/attachment.bin>

Reply by email

Marco van de Voort
to Martin Frb
Martin Frb wrote 20 lines
> > Could you post full source if you haven't already? For a bit of > benchmarking. I just wrote it from the top of my head, and I assumed > 5 instructions for 16-byte would win any time, but haven't verified > anything yet. I had it attached on my last mail. Attached it again here. (3rd procedure / "Utf8LengthAdd") It is only 64bit for now. (And not cleaned up in any way). Also changing "bc >> 7" and "bc and 127" to "moddiv(bc, 255, full, remain)" might save a few more ms. But probably needs larger data to benchmark. If you do work on this, feel free to integrate my code as the baseline for cpu without SSE. Otherwise, it might be a bit until I get to it.

First results: (on an ageing i7-3770, trunk FPC -O4 -Cpcoreavx)

fst 781
fst 781
fst 797
fst 766
pop 656
pop 641
pop 640
pop 641
add 562
add 578
add 563
add 594
asm 297
asm 296
asm 297
asm 297

Asm is nearly fully functional and working, more importantly the remaining issues are constant time and single instruction work, shouldn't influence benchmarking for anything than the shortest sequences.

I'll finish up and post the whole shebang, since more eyes could help, I'm an asm amateur in some regards.

Reply by email

Marco van de Voort
to Bart: On Tue, Dec 28, 2021 at 11:35 PM Martin Frb via lazarus Not surprising that you should…
Bart wrote 15 lines
On Tue, Dec 28, 2021 at 11:35 PM Martin Frb via lazarus wrote: > I have a core I7-8600 > The diff between the old code and popcnt is less significant. > > old: 715 > pop: 695 > > But there is a 3rd way, that is faster. > add: 610 Not surprising that you should come up with a faster solution. IIRC you won both speed contests I had on the forum ;-) Feel free to implement it in LazUtf8.

New unit test, with Martin's integrated. If I play with godbolt, Ryzen zen3 (ryzen 5x00X) is nearly twice as fast in cycles as my Ivy Bridge, so I would like to see some benchmarks from various processors. Also from very old ones (P4 and Clawhammers) to test instruction sets.

I use unaligned loads which afaik on older ( pre Core 1st or 2nd generation) CPUs are costly. (because it loads two caches lines per time)

-------------- next part --------------
//
// (C) 2021 Martin Friebe and Marco van de Voort.
// attempt to accelerate utf8lengthfast which is a length(s) in utf8 codepoints without integrity checking
//
// 4 versions.
// - Original,
// - with popcount and
// - the "add" variant that accumulates 127 iterations of ptrints and only adds
//         the intermeidates outside that loop
// - a SSE2 version loosely inspired by the add variant combined with
//        the core of an existing (branchless) binarization routine for the main loop.
{$mode objfpc}{$H+}
{$asmmode intel}
{define asmdebug}
uses SysUtils,StrUtils;

const
mask3 : array[0..15] of byte = ( $C0,$C0,$C0,$C0, $C0,$C0,$C0,$C0,
$C0,$C0,$C0,$C0,

                                                 $C0,$C0,$C0,$C0);
      mask4       :  array[0..15] of byte  = (   $80,$80,$80,$80,
                                                 $80,$80,$80,$80,
                                                 $80,$80,$80,$80,
                                                 $80,$80,$80,$80);
      mask2       :  array[0..15] of byte  = (   $1,$1,$1,$1,
						 $1,$1,$1,$1,
                                                 $1,$1,$1,$1,
                                                 $1,$1,$1,$1);
// Integer arguments are passed in registers RCX, RDX, R8, and R9.
// Floating point arguments are passed in XMM0L, XMM1L, XMM2L, and XMM3L.
// volatile: RAX, RCX, RDX, R8, R9, R10, R11
// nonvolatile RBX, RBP, RDI, RSI, RSP, R12, R13, R14, and R15 are considered nonvolatile
// volatile xmm0-xmm3 (params) en xmm4,5
// https://msdn.microsoft.com/en-us/library/ms235286.aspx
{$ifdef asmdebug}
function asmutf8length(const s : pchar;res:pbyte;len:integer):int64;
{$else}
function asmutf8length(const s : pchar;len:integer):int64;
{$endif}
begin
 asm
  {$ifndef asmdebug}
    mov r8,rdx
  {$endif}
  // using broadcast etc raises requirements?
  mov rax,r8
  mov r9,r8
  // tuning for short strings:
  // ------
  // test rax,rax
  // je @theend
  // cmp r9,128 // difference between long and short
  // jl @restbytes
  and r9,15
  shr r8,4
  pxor xmm5,xmm5   // always zero
  pxor xmm6,xmm6   // dword counts
  movdqu xmm1,[rip+mask3]
  movdqu xmm2,[rip+mask4]
  movdqu xmm3,[rip+mask2]
  test r8,r8
  je @restbytes

@outer:
mov r10,127 // max iterations
cmp r10,r8 // more or less left?
jl @last // more
mov r10,r8 // less
@last:
sub r8,r10 // iterations left - iterations to do

pxor xmm4,xmm4

@inner:
movdqu xmm0, [rcx]
pand xmm0,xmm1 // mask out top 2 bits
pcmpeqb xmm0,xmm2 // compare with $80.
pand xmm0,xmm3 // change to $1 per byte.
paddb xmm4,xmm0 // add to cumulative

add rcx,16
dec r10
jne @inner

  // process 127 iterations
  movdqa xmm0,xmm4
  PUNPCKLBW xmm0,xmm5           // zero extend to words
  PUNPCKHBW xmm4,xmm5
  paddw xmm0,xmm4               // add, now 8 16-bit words.
  movdqa xmm4,xmm0
  PUNPCKLWD xmm0,xmm5           // zero extend to dwords
  paddd xmm6,xmm0
  PUNPCKHWD  xmm4,xmm5
  paddd xmm6,xmm4               // add both to cumulative
  test r8,r8
  jne @outer
  MOVHLPS  xmm4,xmm6            // move high 8 bytes to low (float->int penalty?)
  paddd xmm6,xmm4               // add both 2*dwords (high doesn't matter)
  pshufd xmm4,xmm6,1           // mov 2nd dword in xmm6 to first in xmm4
  paddd xmm6,xmm4              // add

movd r8d,xmm6 // to int alu reg
sub rax,r8 // subtract from length in bytes. @restbytes:
test r9,r9
je @theend // Done!
@restloop:
movzx r8d, byte [rcx] // unaligned bytes after sse loop mov r10,r8
shr r10,7
not r8
shr r8,6
and r10,r8
sub rax,r10
inc rcx
dec r9
jne @restloop

@theend:
end['xmm5','xmm6']; // volatile registers used.
end;
function countmask(nx:int64):integer;
begin
   nx := (nx and $00FF00FF00FF00FF) + ((nx >>  8) and $00FF00FF00FF00FF);
   nx := (nx and $0000FFFF0000FFFF) + ((nx >> 16) and $0000FFFF0000FFFF);
   result := (nx and $00000000FFFFFFFF) + ((nx >> 32) and $00000000FFFFFFFF);
end;
function UTF8LengthFast(p: PChar; ByteCount: PtrInt): PtrInt;
const
{$ifdef CPU32}
  ONEMASK   =$01010101;
  EIGHTYMASK=$80808080;
{$endif}
{$ifdef CPU64}
  ONEMASK   =$0101010101010101;
  EIGHTYMASK=$8080808080808080;
{$endif}
var
  pnx: PPtrInt absolute p; // To get contents of text in PtrInt blocks. x refers to 32 or 64 bits
  pn8: pint8 absolute pnx; // To read text as Int8 in the initial and final loops
  ix: PtrInt absolute pnx; // To read text as PtrInt in the block loop
  nx: PtrInt;              // values processed in block loop
  i,cnt,e: PtrInt;
begin
  Result := 0;
  e := ix+ByteCount; // End marker
  // Handle any initial misaligned bytes.
  cnt := (not (ix-1)) and (sizeof(PtrInt)-1);
  if cnt>ByteCount then
    cnt := ByteCount;
  for i := 1 to cnt do
  begin
    // Is this byte NOT the first byte of a character?
    Result += (pn8^ shr 7) and ((not pn8^) shr 6);
    inc(pn8);
  end;
  // Handle complete blocks
  for i := 1 to (ByteCount-cnt) div sizeof(PtrInt) do
  begin
    // Count bytes which are NOT the first byte of a character.
    nx := ((pnx^ and EIGHTYMASK) shr 7) and ((not pnx^) shr 6);
    {$push}{$overflowchecks off} // "nx * ONEMASK" causes an arithmetic overflow.
    Result += (nx * ONEMASK) >> ((sizeof(PtrInt) - 1) * 8);
    {$pop}
    inc(pnx);
  end;
  // Take care of any left-over bytes.
  while ix<e do
  begin
    // Is this byte NOT the first byte of a character?
    Result += (pn8^ shr 7) and ((not pn8^) shr 6);
    inc(pn8);
  end;
  Result := ByteCount - Result;
end;
function UTF8LengthPop(p: PChar; ByteCount: PtrInt): PtrInt;
const
{$ifdef CPU32}
  ONEMASK   =$01010101;
  EIGHTYMASK=$80808080;
{$endif}
{$ifdef CPU64}
  ONEMASK   =$0101010101010101;
  EIGHTYMASK=$8080808080808080;
{$endif}
var
  pnx: PPtrInt absolute p; // To get contents of text in PtrInt blocks. x refers to 32 or 64 bits
  pn8: pint8 absolute pnx; // To read text as Int8 in the initial and final loops
  ix: PtrInt absolute pnx; // To read text as PtrInt in the block loop
  nx: PtrInt;              // values processed in block loop
  i,cnt,e: PtrInt;
begin
  Result := 0;
  e := ix+ByteCount; // End marker
  // Handle any initial misaligned bytes.
  cnt := (not (ix-1)) and (sizeof(PtrInt)-1);
  if cnt>ByteCount then
    cnt := ByteCount;
  for i := 1 to cnt do
  begin
    // Is this byte NOT the first byte of a character?
    Result += (pn8^ shr 7) and ((not pn8^) shr 6);
    inc(pn8);
  end;
  // Handle complete blocks
  for i := 1 to (ByteCount-cnt) div sizeof(PtrInt) do
  begin
    // Count bytes which are NOT the first byte of a character.
    nx := ((pnx^ and EIGHTYMASK) shr 7) and ((not pnx^) shr 6);
    {$push}{$overflowchecks off} // "nx * ONEMASK" causes an arithmetic overflow.
    //Result += (nx * ONEMASK) >> ((sizeof(PtrInt) - 1) * 8);
    Result += PopCnt(qword(nx));
    {$pop}
    inc(pnx);
  end;
  // Take care of any left-over bytes.
  while ix<e do
  begin
    // Is this byte NOT the first byte of a character?
    Result += (pn8^ shr 7) and ((not pn8^) shr 6);
    inc(pn8);
  end;
  Result := ByteCount - Result;
end;
function UTF8LengthAdd(p: PChar; ByteCount: PtrInt): PtrInt;
const
{$ifdef CPU32}
  ONEMASK   =$01010101;
  EIGHTYMASK=$80808080;
{$endif}
{$ifdef CPU64}
  ONEMASK   =$0101010101010101;
  EIGHTYMASK=$8080808080808080;
{$endif}
var
  pnx: PPtrInt absolute p; // To get contents of text in PtrInt blocks. x refers to 32 or 64 bits
  pn8: pint8 absolute pnx; // To read text as Int8 in the initial and final loops
  ix: PtrInt absolute pnx; // To read text as PtrInt in the block loop
  nx: PtrInt;              // values processed in block loop
  i,j,cnt,e, bc: PtrInt;
begin
  Result := 0;
  e := ix+ByteCount; // End marker
  // Handle any initial misaligned bytes.
  cnt := (not (ix-1)) and (sizeof(PtrInt)-1);
  if cnt>ByteCount then
    cnt := ByteCount;
  for i := 1 to cnt do
  begin
    // Is this byte NOT the first byte of a character?
    Result += (pn8^ shr 7) and ((not pn8^) shr 6);
    inc(pn8);
  end;
  // Handle complete blocks
  bc := (ByteCount-cnt) div sizeof(PtrInt);
  for j := 1 to bc >> 7 do begin
    nx := 0;
    for i := 0 to 127 do
    begin
      // Count bytes which are NOT the first byte of a character.
      nx += ((pnx^ and EIGHTYMASK) shr 7) and ((not pnx^) shr 6);
      inc(pnx);
    end;
    nx := (nx and $00FF00FF00FF00FF) + ((nx >>  8) and $00FF00FF00FF00FF);
    nx := (nx and $0000FFFF0000FFFF) + ((nx >> 16) and $0000FFFF0000FFFF);
    nx := (nx and $00000000FFFFFFFF) + ((nx >> 32) and $00000000FFFFFFFF);
    Result := Result + nx;
  end;
  if (bc and 127) > 0 then begin
  nx := 0;
  for i := 1 to bc and 127 do
  begin
    // Count bytes which are NOT the first byte of a character.
    nx += ((pnx^ and EIGHTYMASK) shr 7) and ((not pnx^) shr 6);
    inc(pnx);
  end;
    nx := (nx and $00FF00FF00FF00FF) + ((nx >>  8) and $00FF00FF00FF00FF);
    nx := (nx and $0000FFFF0000FFFF) + ((nx >> 16) and $0000FFFF0000FFFF);
    nx := (nx and $00000000FFFFFFFF) + ((nx >> 32) and $00000000FFFFFFFF);
    Result := Result + nx;
  end;
  // Take care of any left-over bytes.
  while ix<e do
  begin
    // Is this byte NOT the first byte of a character?
    Result += (pn8^ shr 7) and ((not pn8^) shr 6);
    inc(pn8);
  end;
  Result := ByteCount - Result;
end;
// one of each pattern.
const pattern : array[0..3] of char = (chr(%11001001),chr(%10001001),
                                       chr(%00001001),chr(%01001001));
function pseudorandomutf8string(len:integer;var cnt:integer):string;
// random string but keep a count of bytes with high value %10
var lcnt:integer;
    i,j:integer;
begin
  setlength(result,len);
  lcnt:=0;
  for i:=1 to length(result) do
    begin
      j:=random(4);
      //j:=i and 3;
      if j=1 then inc(lcnt);
      result[i]:=pattern[j];
    end;
  cnt:=length(result)-lcnt;
end;
var   r : array[0..10000] of byte; // FPC "registers" dialog is poor, we use this for writeln like dumping
procedure testasmutf8length;
const testlen = 64*100;
var s : string;
    i,j,cnt : integer;
    rx : int64;
begin
  randomize;
  s:=pseudorandomutf8string(testlen+Random(50),cnt);
  rx:=asmutf8length(pchar(s),{$ifdef ,{$endif}length(s));
  writeln(inttohex(cnt,2),' = ',inttohex(rx,2),' ',inttohex(length(s)-cnt,2),' = ',inttohex(length(s)-rx,2));  // hex because most register dumps are easier in hex.
  {$ifdef asmdebug}
  for i:=0 to 6 do
      begin
        write(i:2,' ');
        for j:=0 to 3 do
           write(inttohex(pdword(@r[i*16+j*4])^,8), ' ');
        writeln;
      end;
  {$endif}
end;
  var
  a: ansistring;
  t: QWord;
  i, j, ii: Integer;
begin
  {$ifdef asmdebug}
     testasmutf8length;
  {$else}
  a := '???? ???? ?? ??? ???? ???? ?????? ?????? ?????? ??????? ?????? ??? ????? ?????? ?? ??? ???? ????? ??????? ??? ????????? ?';
  a := a+a+A+a+a+A+a+a+A+a+a+A+a+a+A+a+a+A+a+a+A+a+a+A+a+a+A+a+a+A+a+a+A+a+a+A+a+a+A+a+a+A+a+a+A+a+a+A+a+a+A;
  a := a+a+A+a+a+A+a+a+A+a+a+A+a+a+A+a+a+A+a+a+A+a+a+A+a+a+A+a+a+A+a+a+A+a+a+A+a+a+A+a+a+A+a+a+A+a+a+A+a+a+A;
  a := a+a+A+a+a+A+a+a+A+a+a+A+a+a+A+a+a+A+a+a+A+a+a+A+a+a+A+a+a+A+a+a+A+a+a+A+a+a+A+a+a+A+a+a+A+a+a+A+a+a+A;
  a := a+a+A+a+a+A+a+a;
  writeln(Length(a));
  writeln('fst:',UTF8LengthFast(@a[1], Length(a)));
  writeln('pop:',UTF8LengthPop(@a[1], Length(a)));
  writeln('add:',UTF8LengthAdd(@a[1], Length(a)));
  writeln('asm:',asmUTF8Length(@a[1], {$ifdef ,{$endif} Length(a)));
  WriteLn();
  writeln(Length(a) div 8);
  WriteLn();
  for ii := 0 to 1 do begin
  for i := 0 to 3 do begin
    t := GetTickCount64;
    for j := 0 to 19 do
      UTF8LengthFast(@a[1], Length(a));
    t := GetTickCount64 - t;
    writeln('fst ',t);
  end;
  for i := 0 to 3 do begin
    t := GetTickCount64;
    for j := 0 to 19 do
      UTF8LengthPop(@a[1], Length(a));
    t := GetTickCount64 - t;
    writeln('pop ',t);
  end;
  for i := 0 to 3 do begin
    t := GetTickCount64;
    for j := 0 to 19 do
      UTF8LengthAdd(@a[1], Length(a));
    t := GetTickCount64 - t;
    writeln('add ',t);
  end;
  for i := 0 to 3 do begin
    t := GetTickCount64;
    for j := 0 to 19 do
      asmUTF8Length(@a[1], {$ifdef ,{$endif} Length(a));
    t := GetTickCount64 - t;
    writeln('asm ',t);
  end;
  end;
    {$endif}
 {$ifndef FPC}
 if debughooknil then // runtime debugger detection
 {$endif}
   readln;
end.

Reply by email

Marco van de Voort
to Marco van de Voort
Marco van de Voort wrote
?(P4 and Clawhammers) to test instruction sets.

64-bit supporting Pentium 4's of course, since this is a 64-bit only test.

Claw/Sledgehammer is the first iteration of Athlon64 and of the x86_64 architecture as a whole and misses some instructions (like SSE3) that all other 64-bit capable CPUs have.

Reply by email

Alexey Tor.
to Marco van de Voort: New unit test, with Martin's integrated. If I play with godbolt, Ryzen zen3 (ryzen 5x00X)…
New unit test, with Martin's integrated. If I play with godbolt, Ryzen zen3 (ryzen 5x00X) is nearly twice as fast in cycles as my Ivy Bridge, so I would like to see some benchmarks from various processors. Also from very old ones (P4 and Clawhammers) to test instruction sets.

Project utf8lentest raised exception class 'External: SIGSEGV'.

?In file 'utf8lentest.lpr' at line 89:

movdqu xmm0, [rcx]

OS: Linux x64. CPU:

?? vendor_id = "GenuineIntel"
????? (simple synth)? = Intel Core (unknown type) (Sandy Bridge D2/J1/Q0) {Sandy Bridge}, 32nm

Reply by email

Florian Klämpfl
to Alexey Tor.
Alexey Tor. wrote 12 lines
> New unit test, with Martin's integrated. If I play with godbolt, Ryzen > zen3 (ryzen 5x00X) is nearly twice as fast in cycles as my Ivy Bridge, > so I would like to see some benchmarks from various processors. Also > from very old ones (P4 and Clawhammers) to test instruction sets. Project utf8lentest raised exception class 'External: SIGSEGV'. ?In file 'utf8lentest.lpr' at line 89: movdqu xmm0, [rcx] OS: Linux x64. CPU:

Linux uses different calling conventions, please check with the patch below.

Show quoted text 5 lines
?? vendor_id = "GenuineIntel" ????? (simple synth)? = Intel Core (unknown type) (Sandy Bridge D2/J1/Q0) {Sandy Bridge}, 32nm

15c15
< {define asmdebug}
---

{ $define asmdebug}

46c46
< function asmutf8length(const s : pchar;len:integer):int64; ---

function asmutf8length(const s :

pchar;len:int64):int64;assembler;nostackframe;
49d48
< begin
52c51
< mov r8,rdx
---

mov r8,len

89c88
< movdqu xmm0, [rcx]
---

movdqu xmm0, oword ptr [s]

95c94
< add rcx,16
---

add s,16

128c127
< movzx r8d, byte [rcx] // unaligned bytes after sse loop ---

movzx r8d, byte [s] // unaligned bytes after sse loop

135c134
< inc rcx
---

inc s

140c139
< end['xmm5','xmm6']; // volatile registers used.
---

ret

Reply by email

Marco van de Voort
to Florian Klämpfl
Florian Kl?mpfl wrote
Linux uses different calling conventions, please check with the patch below.

Linux is quite generous with the volatile registers, so luckily it matches quite closely.

I first tried the approach of your patch, but [s] has problems on windows, so would require ifdef on every "s"use, so I simply move [s] to rcx

? {$ifndef Windows}
? // we can't use [s] as an alias for the pointer parameter, because the non assembler procedure on Windows
?// changes that into a stack reference. FPC doesn't support non volatile frame management for assembler procs like Delphi does. ? mov rcx,s???????? // rdi
? mov edx,len?????? // rsi
? {$endif}

and the ifdeffing of the assembler procedure on linux vs inline asm block on Windows. Then it works on Linux x86_64.

Funnily, our server AMD Athlon 200GE (Zen1, 3.2GHz?) nearly the exact same timings as my i7-3770 3.4GHz

I did some other minor work after last post, so here is now the entire
program:
-------------- next part --------------
//
// (C) 2021 Martin Friebe and Marco van de Voort.
// attempt to accelerate utf8lengthfast which is a length(s) in utf8 codepoints without integrity checking
//
// 4 versions.
// - Original,
// - with popcount and
// - the "add" variant that accumulates 127 iterations of ptrints and only adds
//         the intermeidates outside that loop
// - a SSE2 version loosely inspired by the add variant combined with
//        the core of an existing (branchless) binarization routine for the main loop.
{$mode objfpc}{$H+}
{$asmmode intel}
{$coperators on}
{define asmdebug}
uses SysUtils,StrUtils;

const
mask3 : array[0..15] of byte = ( $C0,$C0,$C0,$C0, $C0,$C0,$C0,$C0,
$C0,$C0,$C0,$C0,

                                                 $C0,$C0,$C0,$C0);
      mask4       :  array[0..15] of byte  = (   $80,$80,$80,$80,
                                                 $80,$80,$80,$80,
                                                 $80,$80,$80,$80,
                                                 $80,$80,$80,$80);
      mask2       :  array[0..15] of byte  = (   $1,$1,$1,$1,
						 $1,$1,$1,$1,
                                                 $1,$1,$1,$1,
                                                 $1,$1,$1,$1);
// Integer arguments are passed in registers RCX, RDX, R8, and R9.
// Floating point arguments are passed in XMM0L, XMM1L, XMM2L, and XMM3L.
// volatile: RAX, RCX, RDX, R8, R9, R10, R11
// nonvolatile RBX, RBP, RDI, RSI, RSP, R12, R13, R14, and R15 are considered nonvolatile
// volatile xmm0-xmm3 (params) en xmm4,5
// https://msdn.microsoft.com/en-us/library/ms235286.aspx
{$ifdef asmdebug}
function asmutf8length(const s : pchar;len:integer,res:pbyte):int64;
{$else}
function asmutf8length(const s : pchar;len:integer):int64;{$ifndef Windows}assembler; nostackframe;{$endif}
{$endif}
{$ifdef Windows}
begin
{$endif}
 asm
  // tuning for short strings:
  // ------
  {$ifndef Windows}
  // we can't use [s] as an alias for the pointer parameter, because the non assembler procedure on Windows
  // changes that into a stack reference. FPC doesn't support non volatile frame management for assembler procs like Delphi does.
  mov rcx,s         // rdi
  mov edx,len       // rsi
  {$endif}
  test rax,rax
  je @theend
  cmp rdx,128 // threshold between long and short.
  jl @restbytes

mov rax,rdx
mov r10,rcx
and r10,15
mov r9,16
sub r9,r10
and r9,15
test r9,r9
je @nopreloop
sub rdx,r9
@preloop: // roughly 2 cycles per iteration on ivy bridge movzx r11d, byte [rcx] // unaligned bytes after sse loop mov r10,r11
shr r10,7
not r11
shr r11,6
and r10,r11
sub rax,r10
inc rcx
dec r9
jne @preloop
@nopreloop:

mov r9,rdx
and r9,15
shr rdx,4
pxor xmm5,xmm5 // always zero
pxor xmm6,xmm6 // dword counts

  // using broadcast etc raises requirements? -> use constant loads.
  movdqu xmm1,[rip+mask3]
  movdqu xmm2,[rip+mask4]
  movdqu xmm3,[rip+mask2]
  test rdx,rdx
  je @restbytes

@outer:
mov r10,127 // max iterations per inner loop cmp r10,rdx // more or less left?
jl @last // more
mov r10,rdx // less
@last:
sub rdx,r10 // iterations left - iterations to do

pxor xmm4,xmm4

// process 127 iterations (limit of signed int8)

@inner: // +/- 2.2 cycles per iteration for 16 bytes on ivy bridge movdqu xmm0, [rcx]
pand xmm0,xmm1 // mask out top 2 bits
pcmpeqb xmm0,xmm2 // compare with $80.
pand xmm0,xmm3 // change to $1 per byte.
paddb xmm4,xmm0 // add to cumulative

add rcx,16
dec r10
jne @inner

  // SSSE3 vertical adds might help this, but increase CPU reqs.
  movdqa xmm0,xmm4
  PUNPCKLBW xmm0,xmm5           // zero extend to words
  PUNPCKHBW xmm4,xmm5
  paddw xmm0,xmm4               // add, now 8 16-bit words.
  movdqa xmm4,xmm0
  PUNPCKLWD xmm0,xmm5           // zero extend to dwords
  paddd xmm6,xmm0
  PUNPCKHWD  xmm4,xmm5
  paddd xmm6,xmm4               // add both L and H to cumulative 4x dword xmm6 reg
  test rdx,rdx
  jne @outer
  MOVHLPS  xmm4,xmm6            // move high 8 bytes to low (no float->int penalty for move only?)
  paddd xmm6,xmm4               // add both 2*dwords (high doesn't matter)
  pshufd xmm4,xmm6,1           // mov 2nd dword in xmm6 to first in xmm4
  paddd xmm6,xmm4              // add

movd edx,xmm6 // to int alu reg
sub rax,rdx // subtract from length in bytes. @restbytes:
test r9,r9
je @theend // Done!
@restloop:
movzx edx, byte [rcx] // unaligned bytes after sse loop mov r10,rdx
shr r10,7
not rdx
shr rdx,6
and r10,rdx
sub rax,r10
inc rcx
dec r9
jne @restloop

@theend:
{$ifdef Windows}
end['xmm6']; // volatile registers used.
{$endif}
end;
function countmask(nx:int64):integer;
begin
   nx := (nx and $00FF00FF00FF00FF) + ((nx >>  8) and $00FF00FF00FF00FF);
   nx := (nx and $0000FFFF0000FFFF) + ((nx >> 16) and $0000FFFF0000FFFF);
   result := (nx and $00000000FFFFFFFF) + ((nx >> 32) and $00000000FFFFFFFF);
end;
function UTF8LengthFast(p: PChar; ByteCount: PtrInt): PtrInt;
const
{$ifdef CPU32}
  ONEMASK   =$01010101;
  EIGHTYMASK=$80808080;
{$endif}
{$ifdef CPU64}
  ONEMASK   =$0101010101010101;
  EIGHTYMASK=$8080808080808080;
{$endif}
var
  pnx: PPtrInt absolute p; // To get contents of text in PtrInt blocks. x refers to 32 or 64 bits
  pn8: pint8 absolute pnx; // To read text as Int8 in the initial and final loops
  ix: PtrInt absolute pnx; // To read text as PtrInt in the block loop
  nx: PtrInt;              // values processed in block loop
  i,cnt,e: PtrInt;
begin
  Result := 0;
  e := ix+ByteCount; // End marker
  // Handle any initial misaligned bytes.
  cnt := (not (ix-1)) and (sizeof(PtrInt)-1);
  if cnt>ByteCount then
    cnt := ByteCount;
  for i := 1 to cnt do
  begin
    // Is this byte NOT the first byte of a character?
    Result += (pn8^ shr 7) and ((not pn8^) shr 6);
    inc(pn8);
  end;
  // Handle complete blocks
  for i := 1 to (ByteCount-cnt) div sizeof(PtrInt) do
  begin
    // Count bytes which are NOT the first byte of a character.
    nx := ((pnx^ and EIGHTYMASK) shr 7) and ((not pnx^) shr 6);
    {$push}{$overflowchecks off} // "nx * ONEMASK" causes an arithmetic overflow.
    Result += (nx * ONEMASK) >> ((sizeof(PtrInt) - 1) * 8);
    {$pop}
    inc(pnx);
  end;
  // Take care of any left-over bytes.
  while ix<e do
  begin
    // Is this byte NOT the first byte of a character?
    Result += (pn8^ shr 7) and ((not pn8^) shr 6);
    inc(pn8);
  end;
  Result := ByteCount - Result;
end;
function UTF8LengthPop(p: PChar; ByteCount: PtrInt): PtrInt;
const
{$ifdef CPU32}
  ONEMASK   =$01010101;
  EIGHTYMASK=$80808080;
{$endif}
{$ifdef CPU64}
  ONEMASK   =$0101010101010101;
  EIGHTYMASK=$8080808080808080;
{$endif}
var
  pnx: PPtrInt absolute p; // To get contents of text in PtrInt blocks. x refers to 32 or 64 bits
  pn8: pint8 absolute pnx; // To read text as Int8 in the initial and final loops
  ix: PtrInt absolute pnx; // To read text as PtrInt in the block loop
  nx: PtrInt;              // values processed in block loop
  i,cnt,e: PtrInt;
begin
  Result := 0;
  e := ix+ByteCount; // End marker
  // Handle any initial misaligned bytes.
  cnt := (not (ix-1)) and (sizeof(PtrInt)-1);
  if cnt>ByteCount then
    cnt := ByteCount;
  for i := 1 to cnt do
  begin
    // Is this byte NOT the first byte of a character?
    Result += (pn8^ shr 7) and ((not pn8^) shr 6);
    inc(pn8);
  end;
  // Handle complete blocks
  for i := 1 to (ByteCount-cnt) div sizeof(PtrInt) do
  begin
    // Count bytes which are NOT the first byte of a character.
    nx := ((pnx^ and EIGHTYMASK) shr 7) and ((not pnx^) shr 6);
    {$push}{$overflowchecks off} // "nx * ONEMASK" causes an arithmetic overflow.
    //Result += (nx * ONEMASK) >> ((sizeof(PtrInt) - 1) * 8);
    Result += PopCnt(qword(nx));
    {$pop}
    inc(pnx);
  end;
  // Take care of any left-over bytes.
  while ix<e do
  begin
    // Is this byte NOT the first byte of a character?
    Result += (pn8^ shr 7) and ((not pn8^) shr 6);
    inc(pn8);
  end;
  Result := ByteCount - Result;
end;
function UTF8LengthAdd(p: PChar; ByteCount: PtrInt): PtrInt;
const
{$ifdef CPU32}
  ONEMASK   =$01010101;
  EIGHTYMASK=$80808080;
{$endif}
{$ifdef CPU64}
  ONEMASK   =$0101010101010101;
  EIGHTYMASK=$8080808080808080;
{$endif}
var
  pnx: PPtrInt absolute p; // To get contents of text in PtrInt blocks. x refers to 32 or 64 bits
  pn8: pint8 absolute pnx; // To read text as Int8 in the initial and final loops
  ix: PtrInt absolute pnx; // To read text as PtrInt in the block loop
  nx: PtrInt;              // values processed in block loop
  i,j,cnt,e, bc: PtrInt;
begin
  Result := 0;
  e := ix+ByteCount; // End marker
  // Handle any initial misaligned bytes.
  cnt := (not (ix-1)) and (sizeof(PtrInt)-1);
  if cnt>ByteCount then
    cnt := ByteCount;
  for i := 1 to cnt do
  begin
    // Is this byte NOT the first byte of a character?
    Result += (pn8^ shr 7) and ((not pn8^) shr 6);
    inc(pn8);
  end;
  // Handle complete blocks
  bc := (ByteCount-cnt) div sizeof(PtrInt);
  for j := 1 to bc >> 7 do begin
    nx := 0;
    for i := 0 to 127 do
    begin
      // Count bytes which are NOT the first byte of a character.
      nx += ((pnx^ and EIGHTYMASK) shr 7) and ((not pnx^) shr 6);
      inc(pnx);
    end;
    nx := (nx and $00FF00FF00FF00FF) + ((nx >>  8) and $00FF00FF00FF00FF);
    nx := (nx and $0000FFFF0000FFFF) + ((nx >> 16) and $0000FFFF0000FFFF);
    nx := (nx and $00000000FFFFFFFF) + ((nx >> 32) and $00000000FFFFFFFF);
    Result := Result + nx;
  end;
  if (bc and 127) > 0 then begin
  nx := 0;
  for i := 1 to bc and 127 do
  begin
    // Count bytes which are NOT the first byte of a character.
    nx += ((pnx^ and EIGHTYMASK) shr 7) and ((not pnx^) shr 6);
    inc(pnx);
  end;
    nx := (nx and $00FF00FF00FF00FF) + ((nx >>  8) and $00FF00FF00FF00FF);
    nx := (nx and $0000FFFF0000FFFF) + ((nx >> 16) and $0000FFFF0000FFFF);
    nx := (nx and $00000000FFFFFFFF) + ((nx >> 32) and $00000000FFFFFFFF);
    Result := Result + nx;
  end;
  // Take care of any left-over bytes.
  while ix<e do
  begin
    // Is this byte NOT the first byte of a character?
    Result += (pn8^ shr 7) and ((not pn8^) shr 6);
    inc(pn8);
  end;
  Result := ByteCount - Result;
end;
// one of each pattern.
const pattern : array[0..3] of char = (chr(%11001001),chr(%10001001),
                                       chr(%00001001),chr(%01001001));
function pseudorandomutf8string(len:integer;var cnt:integer):string;
// random string but keep a count of bytes with high value %10
var lcnt:integer;
    i,j:integer;
begin
  setlength(result,len);
  lcnt:=0;
  for i:=1 to length(result) do
    begin
      j:=random(4);
      //j:=i and 3;
      if j=1 then inc(lcnt);
      result[i]:=pattern[j];
    end;
  cnt:=length(result)-lcnt;
end;
var   r : array[0..10000] of byte; // FPC "registers" dialog is poor, we use this for writeln like dumping
procedure testasmutf8length;
const testlen = 64*100;
var s : string;
    cnt : integer;
    rx : int64;
begin
  randomize;
  s:=pseudorandomutf8string(testlen+Random(50),cnt);
  rx:=asmutf8length(pchar(s),length(s){$ifdef asmdebug}, at r[0]{$endif});
  writeln(inttohex(cnt,2),' = ',inttohex(rx,2),' ',inttohex(length(s)-cnt,2),' = ',inttohex(length(s)-rx,2));  // hex because most register dumps are easier in hex.
  {$ifdef asmdebug}
  for i:=0 to 6 do
      begin
        write(i:2,' ');
        for j:=0 to 3 do
           write(inttohex(pdword(@r[i*16+j*4])^,8), ' ');
        writeln;
      end;
  {$endif}
end;
  var
  a: ansistring;
  t: QWord;
  i, j, ii: Integer;
begin
  {$ifdef asmdebug}
     testasmutf8length;
  {$else}
  a := '???? ???? ?? ??? ???? ???? ?????? ?????? ?????? ??????? ?????? ??? ????? ?????? ?? ??? ???? ????? ??????? ??? ????????? ?';
  a := a+a+A+a+a+A+a+a+A+a+a+A+a+a+A+a+a+A+a+a+A+a+a+A+a+a+A+a+a+A+a+a+A+a+a+A+a+a+A+a+a+A+a+a+A+a+a+A+a+a+A;
  a := a+a+A+a+a+A+a+a+A+a+a+A+a+a+A+a+a+A+a+a+A+a+a+A+a+a+A+a+a+A+a+a+A+a+a+A+a+a+A+a+a+A+a+a+A+a+a+A+a+a+A;
  a := a+a+A+a+a+A+a+a+A+a+a+A+a+a+A+a+a+A+a+a+A+a+a+A+a+a+A+a+a+A+a+a+A+a+a+A+a+a+A+a+a+A+a+a+A+a+a+A+a+a+A;
  a := a+a+A+a+a+A+a+a;
  writeln(Length(a));
  writeln('fst:',UTF8LengthFast(@a[1], Length(a)));
  writeln('pop:',UTF8LengthPop(@a[1], Length(a)));
  writeln('add:',UTF8LengthAdd(@a[1], Length(a)));
  writeln('asm:',asmUTF8Length(@a[1], Length(a){$ifdef asmdebug}, at r[0]{$endif}));
  WriteLn();
  writeln(Length(a) div 8);
  WriteLn();
  for ii := 0 to 1 do begin
  for i := 0 to 3 do begin
    t := GetTickCount64;
    for j := 0 to 19 do
      UTF8LengthFast(@a[1], Length(a));
    t := GetTickCount64 - t;
    writeln('fst ',t);
  end;
  for i := 0 to 3 do begin
    t := GetTickCount64;
    for j := 0 to 19 do
      UTF8LengthPop(@a[1], Length(a));
    t := GetTickCount64 - t;
    writeln('pop ',t);
  end;
  for i := 0 to 3 do begin
    t := GetTickCount64;
    for j := 0 to 19 do
      UTF8LengthAdd(@a[1], Length(a));
    t := GetTickCount64 - t;
    writeln('add ',t);
  end;
  for i := 0 to 3 do begin
    t := GetTickCount64;
    for j := 0 to 19 do
      asmUTF8Length(@a[1], {$ifdef ,{$endif} Length(a));
    t := GetTickCount64 - t;
    writeln('asm ',t);
  end;
  end;
    {$endif}
 {$ifndef FPC}
 if debughooknil then // runtime debugger detection
 {$endif}
   readln;
end.

Reply by email

John Landmesser
to Marco van de Voort

Perhaps usefui test information from my PC:

******************************************
[john1 at manjaro sdb2]$ ./utf8lentest
234526968
fst:128406168
pop:128406168
add:128406168
asm:128406168

29315871

fst 1365
fst 1367
fst 1366
fst 1366
pop 9990
pop 9990

pop 9997
pop 9981
add 1386
add 1382
add 1386
add 1390
asm 346
asm 346
asm 346
asm 349
fst 1357
fst 1368
fst 1372
fst 1371
pop 10681
pop 6886
pop 6895
pop 6916
add 1247
add 1248
add 1250
add 1248
asm 295
asm 291
asm 291
asm 293
[john1 at manjaro sdb2]$
[john1 at manjaro sdb2]$ inxi -F
System:
? Host: manjaro Kernel: 5.10.84-1-MANJARO x86_64 bits: 64 ??? Desktop: Xfce 4.16.0 Distro: Manjaro Linux
Machine:
? Type: Laptop System: LENOVO product: 81RS v: Lenovo Yoga S740-14IIL ??? serial: <superuser required>
? Mobo: LENOVO model: LNVNB161216 v: SDK0J40709 WIN ??? serial: <superuser required> UEFI: LENOVO v: BYCN39WW date: 05/28/2021 Battery:
? ID-1: BAT0 charge: 62.4 Wh (95.6%) condition: 65.3/62.0 Wh (105.3%) CPU:
? Info: quad core model: Intel Core i7-1065G7 bits: 64 type: MT MCP cache: ??? L2: 2 MiB
? Speed (MHz): avg: 3520 min/max: 400/3900 cores: 1: 3543 2: 3890 3: 2319 ??? 4: 3513 5: 3709 6: 3650 7: 3792 8: 3749
Graphics:
? Device-1: Intel Iris Plus Graphics G7 driver: i915 v: kernel ? Device-2: NVIDIA GP108M [GeForce MX250] driver: nvidia v: 495.44 ? Device-3: Chicony Integrated Camera type: USB driver: uvcvideo ? Display: x11 server: X.Org 1.21.1.2 driver: loaded: modesetting,nvidia ??? unloaded: nouveau resolution: 1: 1920x1080~60Hz 2: 1920x1080~60Hz ? Message: Unable to show advanced data. Required tool glxinfo missing. Audio:
? Device-1: Intel Ice Lake-LP Smart Sound Audio driver: sof-audio-pci ? Sound Server-1: ALSA v: k5.10.84-1-MANJARO running: yes ? Sound Server-2: PipeWire v: 0.3.40 running: yes
Network:
? Device-1: Intel Ice Lake-LP PCH CNVi WiFi driver: iwlwifi ? IF: wlp0s20f3 state: up mac: 04:33:c2:02:de:51
? Device-2: Realtek RTL8153 Gigabit Ethernet Adapter type: USB ??? driver: r8152
? IF: enp0s13f0u1u4 state: up speed: 1000 Mbps duplex: full ??? mac: 4c:e1:73:42:1f:6b
? IF-ID-1: pan1 state: down mac: 7a:5c:6a:f4:06:56 Bluetooth:
? Device-1: Intel AX201 Bluetooth type: USB driver: btusb ? Report: rfkill ID: hci0 state: up address: see --recommends Drives:
? Local Storage: total: 1.86 TiB used: 317.16 GiB (16.7%) ? ID-1: /dev/nvme0n1 vendor: Micron model: MTFDHBA1T0TCK size: 953.87 GiB ? ID-2: /dev/sda type: USB vendor: Western Digital model: WD10EARX-00N0YB0 ??? size: 931.51 GiB
? ID-3: /dev/sdb type: USB vendor: Kingston model: DataTraveler 2.0 ??? size: 14.54 GiB
Partition:
? ID-1: / size: 57.9 GiB used: 35.88 GiB (62.0%) fs: ext4 dev: /dev/nvme0n1p8
? ID-2: /boot/efi size: 259.5 MiB used: 114.1 MiB (44.0%) fs: vfat ??? dev: /dev/nvme0n1p1
Swap:
? ID-1: swap-1 type: partition size: 16.67 GiB used: 0 KiB (0.0%) ??? dev: /dev/nvme0n1p9
Sensors:
? System Temperatures: cpu: 58.0 C mobo: N/A
? Fan Speeds (RPM): N/A
Info:
? Processes: 289 Uptime: 9m Memory: 15.2 GiB used: 2.19 GiB (14.4%) ? Shell: Bash inxi: 3.3.11

*****************************************

Marco van de Voort wrote 31 lines
On 30-12-2021 10:15, Florian Kl?mpfl via lazarus wrote: > > Linux uses different calling conventions, please check with the patch > below. > Linux is quite generous with the volatile registers, so luckily it matches quite closely. I first tried the approach of your patch, but [s] has problems on windows, so would require ifdef on every "s"use, so I simply move [s] to rcx ? {$ifndef Windows} ? // we can't use [s] as an alias for the pointer parameter, because the non assembler procedure on Windows ?// changes that into a stack reference. FPC doesn't support non volatile frame management for assembler procs like Delphi does. ? mov rcx,s???????? // rdi ? mov edx,len?????? // rsi ? {$endif} and the ifdeffing of the assembler procedure on linux vs inline asm block on Windows. Then it works on Linux x86_64. Funnily, our server AMD Athlon 200GE (Zen1, 3.2GHz?) nearly the exact same timings as my i7-3770 3.4GHz I did some other minor work after last post, so here is now the entire program:

Reply by email

Marco van de Voort
to John Landmesser
John Landmesser wrote
Perhaps usefui test information from my PC: 77

Compile with -O4 -Cpcoreavx2 , the others (non asm) will become faster, my guess is? "add" will be about double of asm.

Also, on windows "high performance" as power scheme.? On non windows try to disable power saving for a short while another way. If the first and second runs deviate it is probably the CPU throttling up.

Reply by email

Martin Frb
to Marco van de Voort
Marco van de Voort wrote
Compile with -O4 -Cpcoreavx2 , the others (non asm) will become faster, my guess is? "add" will be about double of asm.

Core I7 8700K

3.3.1 from Dec 10th
3.2.3 from Dec 9th

With fpc 3.3.1:
- fst is worse?
- add gets better

-O4 -Cpcoreavx2

fpc 3.2.3 /?? fpc 3.3.1

fst 594?????? fst 688
fst 578?????? fst 703
fst 578?????? fst 687
fst 562?????? fst 688

pop 485?????? pop 485
pop 500?????? pop 500
pop 500?????? pop 484
pop 484?????? pop 500

add 594?????? add 422
add 578?????? add 438
add 578?????? add 437
add 594?????? add 453

asm 250?????? asm 250
asm 250?????? asm 250
asm 250?????? asm 250
asm 250?????? asm 266

fpc 3.2.3
-O4 -Cpcoreavx?????????? -O4 -CpCOREI

fst 594????????????????? fst 593
fst 578????????????????? fst 579
fst 578????????????????? fst 562
fst 594????????????????? fst 578

pop 500????????????????? pop 500
pop 515????????????????? pop 500
pop 500????????????????? pop 500
pop 485????????????????? pop 485

add 593????????????????? add 593
add 579????????????????? add 578
add 578????????????????? add 594
add 593????????????????? add 594

asm 250????????????????? asm 250
asm 250????????????????? asm 250
asm 235????????????????? asm 250
asm 250????????????????? asm 250

Reply by email