int21h

Použití MMX v Turbo pascalu

Většinou tu píšu o Freepascalu, protože programuji častěji v něm a je mnohem modernější. Jenže hodně lidí zůstalo z různých důvodů u starého Turbo pascalu a nebylo by fér je opomíjet.
Bohužel jedno z omezení TP je, že jeho vestavěný assembler zná pouze instrukce procesoru 286. Instrukce pozdějších procesorů musíme zapisovat pomocí strojového kódu. Toto je hlavní důvod, proč programátoři v TP tak málo používají moderní instrukční sady, jako je například sada MMX. Více informací o MMX najdete jinde, jako dobrý výchozí bod je naše sekce odkazy. Vhodné použití těchto instrukcí drasticky urychlí vaše programy. Já vám ukážu jenom kopírování paměti a vyplňování paměti. MMX toho umí mnohem víc, ale toto je asi nejpotřebnější.
Podívejme se tedy na náhradu procedury Move
Procedure MoveMMX(var odkud; var kam; kolik:word);assembler;
asm
push ds
lds si,odkud
les di,kam
    mov dx,kolik
    shr dx,3
@znovu:
    db 0fh;db 6fh;db 0ch         {MovQ mm1,[si]}
    db 26h;db 0fh;db 7fh;db 0dh  {MovQ es:[di],mm1}
    add si,8
    add di,8
    dec dx
  jnz @znovu
db 0fh;db 77h                    {emms}
pop ds
end;
Vidíte, že kopíruju osm bajtů najednou. Jasně, pokud kopírujete přes FPU, tak jich kopírujete najednou 16, ale FPU pracuje pomaleji, takže to není rychlejší.
Kritické místo se dá zapsat jedině ve strojovém kódu :-( nicméně do komentáře jsem napsal assemblerovský přepis. A nakonec si všimněte, že na konci procedury volám instrukci EMMS, která uklidí stavový registr MMX a opět umožní pracovat s desetinnými čísly. Jednotka pro MMX a FPU totiž sdílejí stejné obvody a jejich instrukce nelze míchat - mezi MMX instrukcí a FPU instrukcí musí být vždy instrukce EMMS. Na prvních MMX procesorech byla mimochodem dost pomalá, na moderních CPU ovšem trvá jenom pár taktů a už nezpomaluje.
Procedurka je to jednoduchá ale neřeší případné přesuny bloků, jejichž délka není násobkem osmi. Proto je vhodné proceduru trošku rozšířit:
Procedure MoveMMX(var odkud; var kam; kolik:word);assembler;
asm
push ds
lds si,odkud
les di,kam
    mov dx,kolik
    mov cx,dx
    shr dx,3
    and cx,7
    cmp dx,0
    jz @malykus
@znovu:
    db 0fh;db 6fh;db 0ch         {MovQ mm1,[si]}
    db 26h;db 0fh;db 7fh;db 0dh  {MovQ es:[di],mm1}
    add si,8
    add di,8
    dec dx
  jnz @znovu
db 0fh;db 77h                    {emms}
@malykus:
rep movsb           {dodela pripadny zbytek}
pop ds
end;
Tímto můžete zcela nahradit standardní Move.
O málo složitější je MMX varianta procedury FillChar. Píšu sem rovnou variantu, která ošetří bloky nedělitelné osmi.
Procedure FillCharMMX(var kam;kolik:word;ceho:byte);assembler;
asm
    mov al,ceho
    mov ah,al
    mov bx,ax
    db 66h;shl ax,16
    mov ax,bx
    db 0fh;db 6eh;db 0c8h              {MovD mm1,eax}
    db 0fh;db 6eh;db 0d0h              {MovD mm2,eax}
    db 0fh;db 73h;db 0f1h;db 20h       {Psllq mm1,32}
    db 0fh;db 0ddh;db 0cah             {Paddusw mm1,mm2}
    mov dx,kolik
    mov cx,dx
    shr dx,3
    and cx,7
    cmp dx,0
    jz @malykus
les di,kam
@znovu:
    db 26h;db 0fh;db 7fh;db 0dh        {MovQ es:[di],mm1}
    add di,8
    dec dx
  jnz @znovu
db 0fh;db 77h                          {emms}
@malykus:
rep stosb
end;
Procedura je složitější tím, že parametr Ceho musíme napřed rozkopírovat na 16 a posléze na 32 bitů a umístit ho do EAX. Jenže MMX registry nejsou 32-bitové, ale 64-bitové, takže musíme ještě uvnitř MMX registrů hodnotu rozkopírovat na 64-bitů. Vidíte, že se to dělá stejným postupem, ale pomocí MMX ekvivalentů instrukcí SHL(psllq) a ADD(paddusw)

Problém je v tom, že je dost dobře možné, že uživatelův počítač instrukce MMX nezná. Znám řadu lidí, kteří doma mají dva počítače. Na novém nadupaném PC mají windows XP a hrají nejmodernější hry a na starším mají DOS nebo windows 9x a hrají tam starší věci a v neposlední řadě na takovýchto strojích používají pascal. Nejjednodušším řešením je, se uživatele prostě zeptat, jestli jeho mašina MMX podporuje. Zodpovědnost nechat na uživateli. Podle toho, co odpoví nastavíme procedurální proměnnou buďto na MoveMMX nebo na standardní Move (nebo na MoveFPU či Move32)
Není to ale příliš profesionální, takže můžeme zkusit podporu MMX detekovat.
K tomu máme instrukci CPUID. Turbo pascal ji samozřejmě nezná, takže ji musíme zapsat ve strojovém kódu. Jenže je to to samé. Instrukci CPUID podporují jen procesory pentium a některé 486. Takže musíme napřed detekovat, zda počítač zná CPUID. Ale aspoň platí, že jestli nezná CPUID, tak určitě nezná ani MMX.
Function Test_CPUID:boolean;
{zjisti, zda pocitac instrukci CPUID vubec podporuje}
begin
inline($66/$9C/$66/$58/$66/$0F/$BA/$E0/$15/$0F/$92/$C2/$66/$0F/$BA/$F8/$15/$66/$50/$66/$9D/
       $66/$9C/$66/$58/$66/$0F/$BA/$E0/$15/$0F/$92/$C1);
{PushfD
Pop eax
    Bt eax,21
    Setc dl
    Btc eax,21
Push eax
PopfD
PushfD
Pop eax
    Bt eax,21
    Setc cl}
asm
    Mov ax,0
    Cmp dl,cl
  Je @konec
    inc ax
@konec:
end;
end;
Tady už hodně přituhlo. Děs hrůza a utrpení. Vážně nechcete přejít na Freepascal? :-/
Když už se jednou budeme pachtit s instrukcí CPUID, tak proč nevyužít její plný potenciál a nedetekovat díky ní všechno co se dá?
Definujme si typ CPU_type, ve kterém budeme mít všechny informace vrácené instrukcí CPUID.
type CPU_type = record
     Vendor       : String[13];
     CPUID        : String;
     Stepping_ID  : Byte;
     Modellnumber : Byte;
     generace     : Byte;
     MMX          : Boolean;
     FPU          : Boolean;
     CMOV         : Boolean;
     _3DNOW       : Boolean;
     End;

A slíbená procedura Get_CPU je zde:
Function Get_CPU(cpu:cpu_type):Boolean;
var      argument  : longint;
         a,p1,p2,p3     : longint;
         CPUResult    : Byte;
         VendorString : Array[0..11] of Char;
         p:Pchar;
Begin
CPU.Vendor    := 'neznamy';
CPU.CPUID     := 'neznama generace procesoru';
cpu.modellnumber:=$FF;
cpu.stepping_ID:=$FF;
cpu.mmx:=false;
cpu.fpu:=false;
cpu.cmov:=false;
cpu._3dnow:=false;
if test_cpuid=false then begin cpu.generace:=3;Exit;end; {nezna CPUID - nejsme asi na pentiu}
asm
db 66h;xor si,si
db 66h;xor ax,ax        { dotaz na nejvyssi mozny argument do EAX - nevidim v tom smysl }
db 0fh;db 0a2h  {CPUID}
db 66h;mov argument.word,ax       { nejvyssi mozny argument EAX - zde nevyuzito }

db 66h;mov p1.word,bx
db 66h;mov p2.word,dx
db 66h;mov p3.word,cx

mov ax,8000h;db 66h;shl ax,16;mov ax,1  {mov eax,80000001h, rozsirene info CPUID}
db 0fh;db 0a2h                          {CPUID}
db 66h;db 0fh;db 0bah;db 0e2h;db 1fh    {Bt edx,31 - 3Dnow?}
db 0fh;db 92h;db 0c0h;                  {setb al}
or al,al
jz @dale1
inc al;mov cpu._3dnow,al
@dale1:

db 66h;xor ax,ax;inc ax                 {mov eax,1}
db 0fh;db 0a2h                          {CPUID}
db 66h;push ax                          {push eax}
db 66h;db 0fh;db 0bah;db 0e2h;db 17h    {Bt edx,23 - MMX?}
db 0fh;db 92h;db 0c0h;                  {setb al}
or al,al
jz @dale2
inc al;mov cpu.mmx,al
@dale2:

db 66h;db 0fh;db 0bah;db 0e2h;db 00h    {Bt edx,0 - FPU?}
db 0fh;db 92h;db 0c0h;                  {setb al}
or al,al
jz @dale3
inc al;mov cpu.fpu,al
@dale3:

db 66h;db 0fh;db 0bah;db 0e2h;db 0Fh    {Bt edx,15 - cMov?}
db 0fh;db 92h;db 0c0h;                  {setb al}
or al,al
jz @dale4
inc al;mov cpu.cmov,al
@dale4:

db 66h;pop ax                           {pop eax}
db 66h;mov bx,ax                        {mov ebx,eax}
And ax,1111b                                         { Stepping_Id }
mov CPU.Stepping_Id,al
db 66h;mov ax,bx                        {mov eax,ebx}

db 66h;shr ax,4                         {shr eax,4}  { Modellnumber }
and ax,1111b
mov CPU.Modellnumber,al
db 66h;mov ax,bx                        {mov eax,ebx}

db 66h;shr ax,8                         {shr eax,8}  { generace }
and ax,1111b
mov CPU.generace,al
end;
case CPU.generace of
  0..2   : CPU.CPUID:= 'Neznama generace procesoru';
  7      : CPU.CPUID:= 'Neznama generace procesoru'; {mozna, mozna Pentium4}
  3      : CPU.CPUID:= '80386';
  4      : CPU.CPUID:= '80486';
  5      : CPU.CPUID:= 'Pentium (TM)';
  6      : CPU.CPUID:= 'Pentium Pro/II/III';
end;
p:=@cpu.vendor;
p^:=#12;inc(p);
move(p1,p^,4);inc(p,4);
move(p2,p^,4);inc(p,4);
move(p3,p^,4);
end;
Pro pochopení této procedury si někde najděte popis instrukce CPUID. Kromě toho se tu zase opakuje martyrium s přepisem instrukcí do strojáku.
Zajímavé je zjištění výrobce procesoru. V poli vendor budete mít buďto GenuineIntel pro procesory od Intelu nebo AuthenticAMD od AMD nebo něco jiného od jiných výrobců.
A to je vše. Strojovému kódu zdar! šmarjá noho.
:-/
2006-11-30 | Laaca
Reklamy: