Delphi i wydajne rotacje bitowe

0

Pascal nie posiada wydajnej funkcji rotacji bitów na zasadzie zapętlenia bajtu czy słowa.
Napisałem dwie proste funkcje RLA i RRA zapętlające przesuwane bity jednak szukam wydajniejszej
formy tych funkcji.Chodzi mi o to aby ich kod napisany był w klasycznym paskalu a nie jako wstawka assemblerowa....
a szybkość realizacji przynajmniej o 30% większa od mojego kodu.
Zamieszczam przykładowy program testujący te funkcje.

unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls;

type
  TForm1 = class(TForm)
    Button1: TButton;
    Button2: TButton;
    procedure Button1Click(Sender: TObject);
    procedure Button2Click(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;

var
  Form1: TForm1;
  BajtTestowy: byte = $0F;

implementation

{$R *.dfm}

//Funkcja rotacji BAJTU w lewo
function RLA(Bajt: byte): byte;
var
Wynik: word;
begin
     Wynik:=0;
     Wynik:=(Bajt shl 1);
     if (Bajt and 128) > 0 then
     begin
          Wynik:=Wynik+1;
     end;
     Result:=Wynik;
end;

//Funkcja rotacji BAJTU w prawo
function RRA(Bajt: byte): byte;
var
Wynik: word;
begin
     Wynik:=0;
     Wynik:=(Bajt shr 1);

     if (Bajt and 1) > 0 then
     begin
          Wynik:=Wynik+128;
     end;
     Result:=Wynik;
end;

////////////////////////////////////////////////////////////////////////////////
{Konwertuje (maksymalnie 64 bity) liczbę dziesiętną na binarną w zapisie ASCII}
//WEJSCIE:  Value = Liczba do przekonwerterowania na ciag binarny ASCII
//          Size =  jak z danej liczby do przekonwerterowania chcemy uzyskac
//                  tylko pewien zakres mlodszych bitow to podajemy tu ilosc
//                  bitow widocznych po konwersji np 7 = 8 bitow widocznych.
//                  Jesli jako parametr Size podamy -1 to uzyskamy tylko tyle
//                  bitów ile mamy do najstarszego nie zerowego bitu tej liczby.
//WYJSCIE: = binarny zapis liczby w formacie ASCII.
//TESTY OK!
function IntToBin(Value: int64;Size: Integer = -1): String;
var
i: Integer;
begin
     Result:='';
     if Size < 0 then
          i:=31
     else
          i:=Size;
     for i:=i downto 0 do
     begin
          if Value and (1 shl i) <> 0 then
               Result:=Result+'1'
          else
               Result:=Result+'0';
     end;

     //Teraz usuwamy z ciagu binarnego wszystkie poprzedzające go zera
     if Size < 0 then Delete(Result,1,Pos('1',Result) - 1);

end;
////////////////////////////////////////////////////////////////////////////////

procedure TForm1.Button1Click(Sender: TObject);
begin
     BajtTestowy:=RLA(BajtTestowy);
     (Sender as TButton).Caption:=IntToBin(BajtTestowy,7);
end;

procedure TForm1.Button2Click(Sender: TObject);
begin
     BajtTestowy:=RRA(BajtTestowy);
     (Sender as TButton).Caption:=IntToBin(BajtTestowy,7);
end;

end.
 
0

Jeżeli N to ilość bitów w słowie to (posługując się mnemonikami x86):

x ROL c = x SHL c || x SHR (N - c)
x ROR c = x SHR c || x SHL (N - c)

SHR nie powiela bitu znaku.

0

Przepisałem to na Pascal ale coś mi nie działa???

//Funkcja rotacji BAJTU w lewo
function RL_A(Bajt: byte): byte;
begin
     Bajt:=((Bajt shl 1) or ((1 - Bajt) shr 1));
Result:=Bajt;
end;
 
0

nie mam delphi żeby sprawdzić ale wydaje mi się że można to zrobić przyjemniej dla procesora wykorzystując assembler

w necie można znaleźć takie funkcje:

function ror(Bajt: Byte): Byte; register; assembler;
asm
  ror al, 1
end;

function rol(Bajt: Byte): Byte; register; assembler;
asm
  rol al, 1
end;
1

http://ideone.com/M5nh6

function rol( c:byte ): byte;
begin
  rol := lewo[c];
end;
 
function ror( c: byte ): byte;
begin
  ror := prawo[c];
end;
0
Xitami napisał(a):

http://ideone.com/M5nh6

function rol( c:byte ): byte;
begin
  rol := lewo[c];
end;
 
function ror( c: byte ): byte;
begin
  ror := prawo[c];
end;

przebijam: http://ideone.com/gEpLi ;D

0

Macie tu całkowity test wydajności moich i tych z forum procedur wyniki są całkiem ciekawe polecam skompilować i zobaczyć...

unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls;

type
  TForm1 = class(TForm)
    Button1: TButton;
    Button2: TButton;
    Button3: TButton;
    
    procedure Button1Click(Sender: TObject);
    procedure Button2Click(Sender: TObject);
    procedure Button3Click(Sender: TObject);

  private
    { Private declarations }
  public
    { Public declarations }
  end;

var
  Form1: TForm1;
  BajtTestowy: byte = $0F;
  Freq, TimeStart, TimeEnd : Int64;
  i: integer;

implementation

{$R *.dfm}

const lewo:array[0..255] of byte = ( 
0,2,4,6,8,10,12,14,16,18,20,22,24,26,28,30,32,
34,36,38,40,42,44,46,48,50,52,54,56,58,60,62,64,
66,68,70,72,74,76,78,80,82,84,86,88,90,92,94,96,
98,100,102,104,106,108,110,112,114,116,118,120,122,124,126,128,
130,132,134,136,138,140,142,144,146,148,150,152,154,156,158,160,
162,164,166,168,170,172,174,176,178,180,182,184,186,188,190,192,
194,196,198,200,202,204,206,208,210,212,214,216,218,220,222,224,
226,228,230,232,234,236,238,240,242,244,246,248,250,252,254,1,
3,5,7,9,11,13,15,17,19,21,23,25,27,29,31,33,
35,37,39,41,43,45,47,49,51,53,55,57,59,61,63,65,
67,69,71,73,75,77,79,81,83,85,87,89,91,93,95,97,
99,101,103,105,107,109,111,113,115,117,119,121,123,125,127,129,
131,133,135,137,139,141,143,145,147,149,151,153,155,157,159,161,
163,165,167,169,171,173,175,177,179,181,183,185,187,189,191,193,
195,197,199,201,203,205,207,209,211,213,215,217,219,221,223,225,
227,229,231,233,235,237,239,241,243,245,247,249,251,253,255);
 
const prawo:array[0..255] of byte = ( 
0,128,1,129,2,130,3,131,4,132,5,133,6,134,7,135,8,
136,9,137,10,138,11,139,12,140,13,141,14,142,15,143,16,
144,17,145,18,146,19,147,20,148,21,149,22,150,23,151,24,
152,25,153,26,154,27,155,28,156,29,157,30,158,31,159,32,
160,33,161,34,162,35,163,36,164,37,165,38,166,39,167,40,
168,41,169,42,170,43,171,44,172,45,173,46,174,47,175,48,
176,49,177,50,178,51,179,52,180,53,181,54,182,55,183,56,
184,57,185,58,186,59,187,60,188,61,189,62,190,63,191,64,
192,65,193,66,194,67,195,68,196,69,197,70,198,71,199,72,
200,73,201,74,202,75,203,76,204,77,205,78,206,79,207,80,
208,81,209,82,210,83,211,84,212,85,213,86,214,87,215,88,
216,89,217,90,218,91,219,92,220,93,221,94,222,95,223,96,
224,97,225,98,226,99,227,100,228,101,229,102,230,103,231,104,
232,105,233,106,234,107,235,108,236,109,237,110,238,111,239,112,
240,113,241,114,242,115,243,116,244,117,245,118,246,119,247,120,
248,121,249,122,250,123,251,124,252,125,253,126,254,127,255);
 
function RL_A( c:byte ): byte;
begin
     RL_A := lewo[c];
end;
 
function RR_A( c: byte ): byte;
begin
     RR_A := prawo[c];
end;

///////////////////////////////////////////////////////////////////////////

function ASM_RL(Bajt: Byte): Byte; register; assembler;
asm
     //mov al,Bajt
     rol al,1
     //mov Result,al
end;

function ASM_RR(Bajt: Byte): Byte; register; assembler;
asm
     //mov al,Bajt
     ror al,1
     //mov Result,al
end;

///////////////////////////////////////////////////////////////////////////

//Funkcja rotacji BAJTU w lewo
function RLA(Bajt: byte): byte;
var
Wynik: word;
begin
     Wynik:=0;
     Wynik:=(Bajt shl 1);
     if (Bajt and 128) > 0 then
     begin
          Wynik:=Wynik+1;
     end;
     Result:=Wynik;
end;

//Funkcja rotacji BAJTU w prawo
function RRA(Bajt: byte): byte;
var
Wynik: word;
begin
     Wynik:=0;
     Wynik:=(Bajt shr 1);

     if (Bajt and 1) > 0 then
     begin
          Wynik:=Wynik+128;
     end;
     Result:=Wynik;
end;

procedure TForm1.Button1Click(Sender: TObject);
begin
     QueryPerformanceFrequency(Freq);
   	QueryPerformanceCounter(TimeStart);

     for i:=1 to 5000000 do
     begin
           BajtTestowy:=RLA(BajtTestowy);
     end;

      for i:=1 to 5000000 do
     begin
           BajtTestowy:=RRA(BajtTestowy);
     end;

     QueryPerformanceCounter(TimeEnd);
	(Sender as TButton).caption:='Wykonanie zajelo: '+ FloatToStr((TimeEnd-TimeStart)/Freq*1000)+ ' ms';
end;


procedure TForm1.Button2Click(Sender: TObject);
begin
     QueryPerformanceFrequency(Freq);
   	QueryPerformanceCounter(TimeStart);

     for i:=1 to 5000000 do
     begin
           BajtTestowy:=RL_A(BajtTestowy);
     end;

     for i:=1 to 5000000 do
     begin
           BajtTestowy:=RR_A(BajtTestowy);
     end;

     QueryPerformanceCounter(TimeEnd);
	(Sender as TButton).caption:='Wykonanie zajelo: '+ FloatToStr((TimeEnd-TimeStart)/Freq*1000)+ ' ms';
end;

procedure TForm1.Button3Click(Sender: TObject);
begin
     QueryPerformanceFrequency(Freq);
   	QueryPerformanceCounter(TimeStart);

     for i:=1 to 5000000 do
     begin
           BajtTestowy:=ASM_RL(BajtTestowy);
     end;

      for i:=1 to 5000000 do
     begin
           BajtTestowy:=ASM_RR(BajtTestowy);
     end;

     QueryPerformanceCounter(TimeEnd);
	(Sender as TButton).caption:='Wykonanie zajelo: '+ FloatToStr((TimeEnd-TimeStart)/Freq*1000)+ ' ms';
end;


end.
 

A oto wyniki:
Moja procedura w czystym pascal'u: Wynik = 34.921 ms
Procedury pozyskane na forum: Wynik = 41.162 ms
Kod assemblerowy: Wynik = 74.364 ms

Zakładam ,że problem kodu assemblerowego wynika z częstego wywołania samej
funkcji ,która widocznie ma dodatkowy nakład kodu na kontekst wywołania wstawki assemblerowej...
Czy ktoś potrafi napisać szybszy kod ???

0
tester_68k napisał(a):

Moja procedura w czystym pascal'u: Wynik = 34.921 ms
Procedury pozyskane na forum: Wynik = 41.162 ms
Kod assemblerowy: Wynik = 74.364 ms

czegoś szybszego niż odwołania po indeksie do tablicy już raczej nie uzyskasz
tylko żeby ten kod był szybszy musisz wyłączyć "range check"

{$R-}

i jeszcze jedyne co możesz usprawnić to dodać do funkcji oznaczenie inline - powinno przyspieszyć wielokrotnie
kilkukrotne przyspieszenie uzyskasz też jeśli parametr funkcji oznaczysz jako const

poza tym - czemu rozbiłeś kod asm na 3 instrukcje

0

chwila chwila wróćmy do korzeni czyli task'a na SPOJ'u - zadanie jest banalne i polega na przesuwaniu indexu tablicy w każdym innym przypadku odp. unikalna_nazwa jest najlepsza.

0
function ASM_RR(Bajt: Byte): Byte; register; assembler;
asm
     mov al,Bajt
     ror al,1
     mov Result,al
end;

Można zrobić tak (pod FPC):

{$ASMMODE INTEL}
function ASM_RR(Bajt: Byte): Byte; register; assembler;nostackframe;
asm
     ror Bajt,1
end;

Sprawdź czy nadal inni są szybsi.. Jak tak to słaby kompilator.

0

Z moich obserwacji wynika, iż widocznie FPC optymalizuje całą pętlę do obliczenia wprost bez pętli, bo ile bym mu iteracji nie podał, to zawsze czas wykonania jest pomijalny: http://ideone.com/5WHJ6

0

1). Co do dyrektywy {$INLINE AUTO} niestety na Delphi7 ona nie działa tz. kod dalej wywoływany jest jako procedura czyli poprzez stos...
2). Wstawienie {$R-} pozostaje bez jakiegokolwiek efektu
3). Wywaliłem z kodu assemblera pobranie parametru do al i zwraceanie parametru poprzez al co dało bardzo szokujące efekty.
Od lat pisze w delphi ale o tym ,ze parametr nr 0 i ten zwracany jest zawsze umieszczany w EAX dowiedziałem się dopiero dziś...

Obecnie wyniki są takie:

Moja procedura w czystym pascal'u: Wynik = 35.428 ms
Procedury pozyskane na forum: Wynik = 41.887 ms
Kod assemblerowy: Wynik = 25.571 ms

0

samo "register" powoduje że "Bajt" jest w "AL", rezultat też jest w "AL", czyli wystarczy samo "ror al, 1" tak jak napisałem kilka postów wcześniej. Pozostałe dwie linijki przypisują AL do AL, a następnie AL do... AL ;)

GDB potwierdza.

Od lat pisze w delphi ale o tym ,ze parametr nr 0 i ten zwracany jest zawsze umieszczany w EAX dowiedziałem się dopiero dziś...

Parametr nr. 0 nie jest umieszczany w EAX poza wywołaniem typu fastcall za które odpowiada dyrektywa register (w FPC assembler dodaje automatycznie register). Dokumentacja FPC opisuje to dokładniej, więc można poczytać jeżeli kogoś interesuje.
Result praktycznie zawsze i wszędzie (włączając inne języki jak C++) jest w EAX.

Jak chcesz pisać b.szybki kod to wiedza o typach wywołań i ich trybach jest potrzebna, tak jak umiejętność pisania wydajnego kodu (nie tylko assemblerowego).

i ewentualnie te "nostackframe" jeżeli delphi to obsługuje?

Jeszcze lepiej dać inline, delphi7 chyba powinno to wspierać. Dzięki temu unikniemy instrukcji call i ret.

BTW. Dziwne że IdeOne używa tak archaicznej wersji FPC jak 2.2.0 kiedy 2.6.0 jest najnowsza.

0
{$MODE DELPHI} { integer 32 bit }
{$inline on}
{$r-}
const lewo:array[0..255] of byte = ( 
0,2,4,6,8,10,12,14,16,18,20,22,24,26,28,30,32,
34,36,38,40,42,44,46,48,50,52,54,56,58,60,62,64,
66,68,70,72,74,76,78,80,82,84,86,88,90,92,94,96,
98,100,102,104,106,108,110,112,114,116,118,120,122,124,126,128,
130,132,134,136,138,140,142,144,146,148,150,152,154,156,158,160,
162,164,166,168,170,172,174,176,178,180,182,184,186,188,190,192,
194,196,198,200,202,204,206,208,210,212,214,216,218,220,222,224,
226,228,230,232,234,236,238,240,242,244,246,248,250,252,254,1,
3,5,7,9,11,13,15,17,19,21,23,25,27,29,31,33,
35,37,39,41,43,45,47,49,51,53,55,57,59,61,63,65,
67,69,71,73,75,77,79,81,83,85,87,89,91,93,95,97,
99,101,103,105,107,109,111,113,115,117,119,121,123,125,127,129,
131,133,135,137,139,141,143,145,147,149,151,153,155,157,159,161,
163,165,167,169,171,173,175,177,179,181,183,185,187,189,191,193,
195,197,199,201,203,205,207,209,211,213,215,217,219,221,223,225,
227,229,231,233,235,237,239,241,243,245,247,249,251,253,255);

function RL_A( c:byte ): byte; inline;
begin
     RL_A := lewo[c];
end;

function rol(b:byte):byte; inline;
begin
  if b>127 then rol := 1+b shl 1
  else rol:= b shl 1;
end;

function RLA(Bajt: byte): byte; inline;
var
Wynik: word;
begin
     Wynik:=0;
     Wynik:=(Bajt shl 1);
     if (Bajt and 128) > 0 then
     begin
          Wynik:=Wynik+1;
     end;
     Result:=Wynik;
end;

function rll(Bajt: Byte): Byte; register; assembler; inline;
asm
  rol al, 1
end;

var i:integer; b:byte;
begin
  writeln(sizeof(i));
  b:=123;
  for i:=1 to 200000000 do
    b:= r...(b);
  writeln(b)
end.

Czasy. rol rl_a rla rll b:=code>Czasy`. rol rl_a rla rll b:=b
. 1.98 2.11 2.94 0.80 /-> 0.69
inline 1.88 2.09 2.84 0.65 <-/ :-)

Ciekawy czas gdy w pętli zostanie tylko średnik. Zadziwiający kompilator.
0

ASM

Super, tylko ten kod rll() jest niepewny.
To że argument "wpada" do AL nie jest nigdzie chyba spisane w wymogach do kompilatora?
A co za tym idzie może się zmieniać - w zależności od systemu, kompilatora czy nawet miejsca użycia.
No i działa tylko na x86.

Pascal
"Na oko" najlepiej wygląda funkcja pierwszy raz podana chyba przez Xitami - rol().
Z tym że zamiast dodawać jeden wykonywałbym or, czyli:

function rol(b:byte):byte; inline;
begin
if b>127 then
Result := (b shl 1) or 1;
else
Result := b shl 1;
end;

(nie testowane)

Natomiast warto też czasami poczytać trochę w internecie zanim się coś zacznie wymyślać, FPC przynajmniej od wersji 2.6 ma te funkcje - patrz rolbyte(), rorbyte():

http://wiki.freepascal.org/FPC_New_Features_2.6.0#ROL.2FROR_intrinsics

0

Super, tylko ten kod rll() jest niepewny.
To że argument "wpada" do AL nie jest nigdzie chyba spisane w wymogach do kompilatora?
A co za tym idzie może się zmieniać - w zależności od systemu, kompilatora czy nawet miejsca użycia.
No i działa tylko na x86.

Kłamiesz: http://www.freepascal.org/docs-html/ref/refsu72.html#x175-18500014.9.12
Nie wprowadzaj innych w błąd.

Natomiast warto też czasami poczytać trochę w internecie zanim się coś zacznie wymyślać, FPC przynajmniej od wersji 2.6 ma te funkcje - patrz rolbyte(), rorbyte():

http://wiki.freepascal.org/FPC_New_Features_2.6.0#ROL.2FROR_intrinsics

Na pierwszej stronie już o tym mówiłem.

Ciekawy czas gdy w pętli zostanie tylko średnik. Zadziwiający kompilator.

Bo wtedy pewnie jest generowany stack na tym i całe wywołanie.

0
-123oho napisał(a):

Super, tylko ten kod rll() jest niepewny.
To że argument "wpada" do AL nie jest nigdzie chyba spisane w wymogach do kompilatora?
A co za tym idzie może się zmieniać - w zależności od systemu, kompilatora czy nawet miejsca użycia.
No i działa tylko na x86.

Kłamiesz: http://www.freepascal.org/docs-html/ref/refsu72.html#x175-18500014.9.12
Nie wprowadzaj innych w błąd.

Zanim zaczniesz zarzucać komuś kłamstwo przeczytaj ze zrozumieniem to co napisałem.
Żeby nie wymieniać po próżnicy pustosłowia zwrócę uwagę na słowo "chyba".

A potem proponuje jeszcze zajrzeć tutaj:
http://wiki.freepascal.org/Platform_list

-123oho napisał(a):

Natomiast warto też czasami poczytać trochę w internecie zanim się coś zacznie wymyślać, FPC przynajmniej od wersji 2.6 ma te funkcje - patrz rolbyte(), rorbyte():

http://wiki.freepascal.org/FPC_New_Features_2.6.0#ROL.2FROR_intrinsics

Na pierwszej stronie już o tym mówiłem.

To nie rozumiem po co to bicie piany na dwie strony i do tego nikt tego nie przetestował, a może znów czegoś nie zauważyłem?

0

Zanim zaczniesz zarzucać komuś kłamstwo przeczytaj ze zrozumieniem to co napisałem.
Żeby nie wymieniać po próżnicy pustosłowia zwrócę uwagę na słowo "chyba".

"Super, tylko ten kod rll() jest niepewny." - tutaj nie ma słowa chyba. A kod jest poprawny. Dlatego kłamiesz.
Nie chcę się czepiać ale mówisz mimo że wiedzy ci brakuje w tym temacie, może się nie wypowiadaj zamiast postować głupoty. I nie żebym chciał być chamski, po prostu mało wiesz na temat konwencji wywołań w FPC co już pokazałeś.

A potem proponuje jeszcze zajrzeć tutaj:
http://wiki.freepascal.org/Platform_list

Czy błędem jest to że kod assemblera x86 nie działa na innych platformach? Wątpię. To nie jest błąd, po prostu ograniczenie zastosowania tej procedury do x86. Wydajność to zazwyczaj odwrotność przenośności.

To nie rozumiem po co to bicie piany na dwie strony i do tego nikt tego nie przetestował, a może znów czegoś nie zauważyłem?

Nie sądzę żeby było to wydajne wyjątkowo. Pewnie implementacja obejmuje wszystkie platformy i jest wolniejsza.

0

Proponuję powołać niezależną komisję śledczą:
user image

0
vpiotr napisał(a):
-123oho napisał(a):

Natomiast warto też czasami poczytać trochę w internecie zanim się coś zacznie wymyślać, FPC przynajmniej od wersji 2.6 ma te funkcje - patrz rolbyte(), rorbyte():

http://wiki.freepascal.org/FPC_New_Features_2.6.0#ROL.2FROR_intrinsics

Na pierwszej stronie już o tym mówiłem.

To nie rozumiem po co to bicie piany na dwie strony i do tego nikt tego nie przetestował, a może znów czegoś nie zauważyłem?

może dlatego że to funkcja w FPC, a w temacie mowa o delphi? ;>

1
unikalna_nazwa napisał(a):
vpiotr napisał(a):
-123oho napisał(a):

Natomiast warto też czasami poczytać trochę w internecie zanim się coś zacznie wymyślać, FPC przynajmniej od wersji 2.6 ma te funkcje - patrz rolbyte(), rorbyte():

http://wiki.freepascal.org/FPC_New_Features_2.6.0#ROL.2FROR_intrinsics

Na pierwszej stronie już o tym mówiłem.

To nie rozumiem po co to bicie piany na dwie strony i do tego nikt tego nie przetestował, a może znów czegoś nie zauważyłem?

może dlatego że to funkcja w FPC, a w temacie mowa o delphi? ;>

Często temat nazywa się 'delphi' gdy chodzi o FPC. Zresztą każdy kto używa takiego śmiecia jak delphi7 powinien przerzucić się na Lazarusa, bo jest otwarty, nowocześniejszy i ma fajny splashscreen.

Btw. @vpiotr źle przeczytałem niepewny jako niepoprawny, mój błąd. Co prawda błąd nadal masz ale nie powinienem był aż tak reagować :P . Więc sorry.

0
  1. zwykle w wstawkach ASM daje się przesyłania z argumentów wysokopoziomowych do rejestrów, prawdopodobnie dlatego że dobry kompilator powinien żonglować rejestrami w zależności od miejsca wywołania, zwłaszcza dla funkcji inline

Użycie register wymusza na kompilatorze użycie tych rejestrów do przekazania parametrów, raczej chodzi o to że inline powoduje wstawienie kodu bez calla. Zawsze też można napisać ror Bajt,1 wtedy nawet gdy rejestr się zmieni to wyjście będzie poprawne. Generalnie to inline i assembler to problematyczna sprawa, FPC nie jest tutaj najmądrzejszy (patrz dalej).

  1. opis słowa "register" nie wskazuje jednoznacznie że pierwszy / jedyny argument zawsze trafia do EAX, do tego nie bierze pod uwagę innych CPU niż x86, w związku z czym nie polegałbym na nim.

Sądzę że FPC ma jakieś swoje rejestry których używa na innych platformach, ale trzeba by poszukać żeby znaleźć. Z dokumentacji jasno wynika że jest to zgodność z Delphi i register powoduje że pierwsze 3 parametry są przekazywane w EAX,ECX i EDX. Można się domyślić że w przypadku jednoparametrowej funkcji pierwszy argument trafia do EAX. Tak jak się można domyślić tak i jest w rzeczywistości. Nie wiem czemu zakładasz że coś ma się nie zgadzać z dokumentacją.

blah.lpr(18,4) Warning: "assembler" not yet supported inside inline procedure\function
blah.lpr(18,4) Warning: Inlining disabled
Projekt "blah" poprawnie zbudowany. :)

Zgadnij dlaczego właśnie assembler (który w FPC domyślnie daje register) nie jest wspierane z inline. Właśnie przez niemożność dobrej optymalizacji oraz przez trudności z przewidywaniem które rejestry zostaną zmodyfikowane. nostackframe działa. Z tego wynika że w FPC najlepiej jest dać kod bezpośrednio zamiast jako procedurę.

Jasno z tego wszystkiego wynika że ta procedura jest bezpieczna niezależnie od miejsca wywołania.
Zresztą tutaj masz krótki kod maszynowy (maksymalna optymalizacja, użyte nostackframe):

00401555 b001                     mov    $0x1,%al
00401557 e8e4ffffff               call   0x401540 <ASM_RR>
[...]
00401540 d0c8                     ror    %al
00401542 c3                       ret    

Nie ma sposobu żeby coś się tutaj spierniczyło.

w dodatku kompilator tłumaczył go jako przepisywanie rejestru A dwukrotnie samego do siebie

FPC po prostu zamienia nazwy parametrów na rejestry w przypadku wywołania register, programista bierze w takim przypadku odpowiedzialność za odpowiednie ich wykorzystanie na siebie.

0
-123oho napisał(a):
  1. opis słowa "register" nie wskazuje jednoznacznie że pierwszy / jedyny argument zawsze trafia do EAX, do tego nie bierze pod uwagę innych CPU niż x86, w związku z czym nie polegałbym na nim.

Sądzę że FPC ma jakieś swoje rejestry których używa na innych platformach, ale trzeba by poszukać żeby znaleźć. Z dokumentacji jasno wynika że jest to zgodność z Delphi i register powoduje że pierwsze 3 parametry są przekazywane w EAX,ECX i EDX. Można się domyślić że w przypadku jednoparametrowej funkcji pierwszy argument trafia do EAX. Tak jak się można domyślić tak i jest w rzeczywistości. Nie wiem czemu zakładasz że coś ma się nie zgadzać z dokumentacją.

Nic nie zakładam, po prostu dokumentacja jest w tym miejscu niedokładna i nie mówi to co myślisz że mówi. Rejestry są podane, ale nie wiadomo w jakiej kolejności będą użyte. A jeśli nawet wiadomo, to dokumentacja tę kolejność podaje źle:

Powinno być EAX, ECX, EDX - tak jak napisałeś i tak jak dokumentacja mówi, ale:

function rll1(Bajt1, Bajt2: Byte): Byte; register; assembler;
asm
  rol al, 1
  rol dl, 1
end;

procedure RunTest;
const a = 122;
var
  r, s: byte;
begin
  r := rll1(a, 12);
{
  s := rll1(r, 12);
  writeln('s: ', s);
}
end;

ale w D7 tłumaczy się na:

mov dl, $0c
mov al, $7a
call rll1
...

Czyli EAX, EDX, ECX.

Skąd ten rozjazd? Nie wiem, w każdym razie na Wikipedii podają dobrze:
http://en.wikipedia.org/wiki/X86_calling_conventions#register

to inaczej:
http://en.wikipedia.org/wiki/X86_calling_conventions#Borland_fastcall

czyli właśnie EAX, EDX, ECX.

W FPC jest jakaś inna składnia ASM i nie mam kodu żeby przetestować to, ale coś takiego:

function rll1(Bajt1, Bajt2: Byte): Byte; register;
begin
  if Bajt1>127 then
      Result := (Bajt1 shl 1) + 1
  else
      Result := Bajt1 shl 1;

  if Bajt2>127 then
      Result := Result + (Bajt2 shl 1) + 1
  else
      Result := Result + Bajt2 shl 1;
end;

procedure RunTest;
const a = 122;
var
  r, s: byte;
begin
  r := rll1(a, 12);
{
  s := rll1(r, 12);
  writeln('s: ', s);
}
end;

w ogóle nie podaje argumentów do EAX...

Dlatego proponuje trochę większą ostrożność w stosunku do "register"...

-123oho napisał(a):
blah.lpr(18,4) Warning: "assembler" not yet supported inside inline procedure\function
blah.lpr(18,4) Warning: Inlining disabled
Projekt "blah" poprawnie zbudowany. :)

Zgadnij dlaczego właśnie assembler (który w FPC domyślnie daje register) nie jest wspierane z inline. Właśnie przez niemożność dobrej optymalizacji oraz przez trudności z przewidywaniem które rejestry zostaną zmodyfikowane. nostackframe działa. Z tego wynika że w FPC najlepiej jest dać kod bezpośrednio zamiast jako procedurę.

Zmylił mnie kod podany przez Xitami (nie kompiluje się pod D7):

function rll(Bajt: Byte): Byte; register; assembler; inline;
asm
  rol al, 1
end;

Nie miałem przy sobie kompilatora, ale jeśli jest tak jak piszesz to OK.

0

Trochę twój post niezrozumiały ale spróbujmy coś odpowiedzieć:

W FPC jest jakaś inna składnia ASM i nie mam kodu żeby przetestować to, ale coś takiego:

{ASMMODE INTEL} ?
Kod który dałeś działa, uzywa al i dl.

Dlatego proponuje trochę większą ostrożność w stosunku do "register"...

Generalnie budowa podstaw różni się w przypadku innych kompilatorów, więc trzeba zawsze uważać przy rzeczach lowlevel i zmienianiu kompilatorów.

Powinno być EAX, ECX, EDX - tak jak napisałeś i tak jak dokumentacja mówi, ale:

FPC robi EAX,EDX,ECX czyli dobrze, widać w dokumentacji był błąd.

Generalnie popatrzyłem na FPC i register i widzę że jest to dosyć niedopracowane:

  1. Użycie var przed zmienną nie zmienia kodu.
  2. Użycie nostackframe i result nie zmienia zachowania FPC który nadal usiłuje zapisać result na stosie. (to akurat nie dotyczy register bezpośrednio)
  3. Użycie int64 z result powoduje że calle odkłada parametry na stos, ale procedura używa rejestrów ecx i ebx.
    Zareportuję te bugi niedługo.

Nie mniej kod działa więc teraz już chyba szukamy dziury w całym :P . Może niech autor się odezwie czy coś jeszcze potrzebuje.

0
-123oho napisał(a):

Generalnie popatrzyłem na FPC i register i widzę że jest to dosyć niedopracowane:

  1. Użycie var przed zmienną nie zmienia kodu.
  2. Użycie nostackframe i result nie zmienia zachowania FPC który nadal usiłuje zapisać result na stosie. (to akurat nie dotyczy register bezpośrednio)
  3. Użycie int64 z result powoduje że calle odkłada parametry na stos, ale procedura używa rejestrów ecx i ebx.
    Zareportuję te bugi niedługo.

Pospieszyłem się, okazuje się że FPC głupieje gdy do tego dorzucimy assembler, inaczej mimo że wszystko wygląda dziwnie to działa. Przyjże się temu dokładniej i zareportuję ważniejsze bugi...

1 użytkowników online, w tym zalogowanych: 0, gości: 1