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
db 26h;db 0fh;db 7fh;db 0dh
add si,8
add di,8
dec dx
jnz @znovu
db 0fh;db 77h
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
db 26h;db 0fh;db 7fh;db 0dh
add si,8
add di,8
dec dx
jnz @znovu
db 0fh;db 77h
@malykus:
rep movsb
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
db 0fh;db 6eh;db 0d0h
db 0fh;db 73h;db 0f1h;db 20h
db 0fh;db 0ddh;db 0cah
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
add di,8
dec dx
jnz @znovu
db 0fh;db 77h
@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;
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);
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;
asm
db 66h;xor si,si
db 66h;xor ax,ax
db 0fh;db 0a2h
db 66h;mov argument.word,ax
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
db 0fh;db 0a2h
db 66h;db 0fh;db 0bah;db 0e2h;db 1fh
db 0fh;db 92h;db 0c0h;
or al,al
jz @dale1
inc al;mov cpu._3dnow,al
@dale1:
db 66h;xor ax,ax;inc ax
db 0fh;db 0a2h
db 66h;push ax
db 66h;db 0fh;db 0bah;db 0e2h;db 17h
db 0fh;db 92h;db 0c0h;
or al,al
jz @dale2
inc al;mov cpu.mmx,al
@dale2:
db 66h;db 0fh;db 0bah;db 0e2h;db 00h
db 0fh;db 92h;db 0c0h;
or al,al
jz @dale3
inc al;mov cpu.fpu,al
@dale3:
db 66h;db 0fh;db 0bah;db 0e2h;db 0Fh
db 0fh;db 92h;db 0c0h;
or al,al
jz @dale4
inc al;mov cpu.cmov,al
@dale4:
db 66h;pop ax
db 66h;mov bx,ax
And ax,1111b
mov CPU.Stepping_Id,al
db 66h;mov ax,bx
db 66h;shr ax,4
and ax,1111b
mov CPU.Modellnumber,al
db 66h;mov ax,bx
db 66h;shr ax,8
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';
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.
:-/