
          ͻ
                    Tutorial de Assembler de Adam Hyde 1.0        Ŀ
                                                                   
                                  PARTE VII                        
                      Traduzido por Renato Nunes Bastos            
          ͼ 
            


Verso   :  1.0
Data     :  01-05-1996
Contato  :  blackcat@vale.faroc.com.au
            http://www.faroc.com.au/~blackcat
;Renato  :  bastos@lci.ufrj.br
            krull@geocities.com
            http://www.geocities.com/SiliconValley/Park/3174

 

Oi de novo, e bem-vindo  stima parte dos Tutoriais de Assembler.
Estes tutoriais parecem estar saindo sem regularidade, mas pessoas esto me
pedindo coisas que eu nunca fiz, e eu ainda estou trabalhando em projetos
meus. Espero cuspir estes tutoriais quinzenalmente. 

Agora, esta semana vamos cobrir dois tpicos muito importantes. Quando eu
comecei a brincar com Assembler eu logo vi que o Turbo Pascal, (a linguagem
com que eu trabalhava at ento), tinha poucas limitaes - uma delas  que
ela era, e ainda , uma linguagem de 16 bits. Isso significava que se eu
quisesse brincar com escritas super-rpidas em 32 bits, eu no poderia.
Nem mesmo com seu prprio Assembler (bem, no facilmente).

O que eu precisava fazer era escrever cdigo separadamente 100% em Assembler
e linkar ao Turbo. Isso no  uma tarefa particularmente difcil, e uma das
que eu vou tentar ensinar a voc hoje.

A outra vantagem de escrever rotinas em Assembler puro  que voc tambm pode
linkar o cdigo objeto resultante a outra linguagem de alto nvel, como o C.

 

         Ŀ
                                                                   
         ESCREVENDO CDIGO EXTERNO PARA SUA LINGUAGEM DE ALTO NVEL
                                                                   
         


Antes de comearmos, voc precisa de uma idia do que so chamadas far e near.
Se voc j sabe, ento pule essa pequena seo.

Como discutimos antes, o PC tem uma arquitetura segmentada. Como voc sabe,
voc s pode acessar um segmento de 64K de cada vez. Agora se voc est
trabalhando com cdigo de menos de 64K de tamanho, ou em uma linguagem que
cuida de todas as preocupaes para voc, voc no precisa de se preocupar
tanto. Contudo, trabalhando em Assembler, precisamos sim.

Imagine que tenhamos o seguinte programa caregado na memria:


       
                     Ŀ
                                              
  64K         Ĵ       ROTINA DOIS       Ŀ
                                                
                       
                                                   
               Ŀ        
                                                
              Ĵ       ROTINA UM         Ŀ 
                                                
                       
                                                  
  64K                Ŀ  
                                               
          EntradaĴ   PROGRAMA PRINCIPAL       
                                            
                     
                                             
                                           Sada


Quando um JMP for executado para transferir o controle para a Rotina Um, esse
ser uma chamada near(perto). Ns no deixamos o segmento em que o corpo
principal do programa est localizado, e assim quando o JMP ou CALL 
executado, e CS:IP  mudado por JMP, s o IP precisa ser mudado, no CS.

O offset muda, mas o segmento no.

Agora, pular para a Rotina Dois seria diferente. Isto deixa o segmento
corrente, e assim ambas as partes do par CS:IP precisaro de ser alteradas.
Isto  uma chamada far (longe).

O problema ocorre quando a CPU encontra um RET ou RETF no fim da chamada.
Digamos que voc, por acidente, colocou RET no fim da Rotina Dois, ao invs
de RETF. Como a CPU viu RET, ela s tiraria IP da pilha, e assim, sua
mquina travaria, provavelmente, j que CS:IP estaria apontando para um lixo.

Este ponto  especialmente importante quando for linkar a uma linguagem de
alto nvel. Seja l quando for que voc escrever um cdigo em Assembly e
linkar, digamos, ao Pascal, lembre-se de usar a diretiva de compilao
{$F+}, mesmo se no foi uma chamada FAR. Deste modo, depois de o Turbo
chamar a rotina, ele tira CS e IP da pilha, e tudo vai bem.

Falhas em fazer isso so problemas seus!


 

OK, de volta ao modelo em Assembly puro do Tutorial Trs. Eu no me lembro
direito, mas eu acho que era alguma coisa assim:


    DOSSEG
    .MODEL SMALL
    .STACK 200h
    .DATA
    .CODE

START:


END START


Agora, acho que  hora de vocs pularem um grau no uso daquele esqueleto.
Vejamos outros modos de arrumar uma rotina-esqueleto.



    DATA     SEGMENT WORD PUBLIC

    DATA     ENDS


    CODE     SEGMENT WORD PUBLIC
     ASSUME  CS:CODE, DS:DATA

    CODE     ENDS

    END

Este , obviamente, um esqueleto diferente. Note como eu omiti o ponto antes
de DATA e CODE. Dependendo de que Assembler/Linker voc usar, voc pode
precisar do ponto ou no. TASM, o Assembler que eu uso, aceita os dois
formatos, ento, pegue um com que voc e seu assembler estejam felizes.

Note tambm o uso de DATA  SEGMENT WORD PUBLIC. Primeiramente, WORD diz
ao Assembler para alinhar o segmento em limites de word. 

FATO ENGRAADO: Voc no precisa se preocupar com isso por enquanto, pois o
                Turbo Pascal faz isso de qualquer modo, assim, colocar
                BYTE ao invs de word no faria diferena nenhuma. :)

PUBLIC permite ao compilador que voc usar, acessar quaisquer variveis no
segmento de dados. Se voc no quer que seu compilador tenha acesso a qualquer
varivel que voc declarar, ento apenas omita isso. Se voc no precisar
de acessar o segmento de dados, ento esquea o segmento de dados todo.

Agora, o segmento de cdigo. Geralmente, voc vai precisar incluir isso em
todo o cdigo que voc escrever. :) A sentena ASSUME tambm ser um padro
em tudo que voc vai trabalhar. Voc tambm pode esperar ver CSEG e DSEG
ao invs de CODE e DATA. Note de novo que ele  declarado como PUBLIC.
 nele que todas as nossas rotinas vo.


 

                 Ento, como eu declaro procedures externas?


Ok, por exemplo, vamos usar umas poucas rotinas simples similares quelas
na biblioteca do modo 13H do PASCAL (disponvel na minha homepage).

Se voc se lembrar, a procedure se parece um pouco com isso:


    Procedure PutPixel(X, Y : Integer; Color : Byte);

    Procedure InitMCGA;

    Procedure Init80x25;


Ajustando isso no nosso esqueleto, temos:


    CODE     SEGMENT WORD PUBLIC
     ASSUME  CS:CODE DS:DATA

     PUBLIC  PutPixel
     PUBLIC  InitMCGA
     PUBLIC  Init80x25

    CODE     ENDS


    END

Agora, tudo o que temos a fazer  codific-los. Mas, espere um minuto - a
rotina PutPixel tem PARMETROS! Como us-los em cdigo externo??

Isto  um macete. O que fazemos  colocar os valores na pilha, simplesmente
dizendo -- PutPixel(10,25,15); -- j faz isso para ns.  tirar eles de l 
que  o mais difcil. O que eu geralmente fao, e sugiro a vocs fazer, 
se certificar de DECLARAR TODAS AS PROCEDURES EXTERNAS COMO FAR. Isso faz
as coisas trabalharem com a pilha mais fcil.

FATO ENGRAADO: Lembre que a primeira cois a entrar na pilha  a LTIMA A
                SAIR. :)

Quando voc chamar a Putpixel, a pilha estar mudada. Como isso  uma chamada
FAR, os primeiros 4 bytes so CS:IP. Os bytes da em diante so os nossos
parmetros.

Para encurtar a histria, digamos que a pilha se parea com isso:

   00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ...

Depois de chamar -- PutPixel(10, 20, 15); -- um tempo depois, ela pode se
parecer com isso:


   4C EF 43 12 0F 00 14 00 0A 00   9E F4 3A 23 1E 21 ...

   ^^^^^^^^^^^ ^^^^^ ^^^^^ ^^^^^   ^^^^^^^^^^^^^^^^^
      CS:IP    Color   Y     X        Algum lixo

Agora, para complicar as coisas, a CPU armazena words na pilha com a PARTE
MENOS SIGNIFICATIVA PRIMEIRO. Isso no nos atrapalha muito, mas se voc
ficar andando por a com um debugger sem saber disso, voc vai ficar mesmo
confuso.

Note tambm que quando o Turbo Pascal pe um tipo de dado byte na pilha, ele
te chupa DOIS BYTES, NO UM. Voc no adora o modo como o PC  organizado? ;)

Agora, tudo que eu disse at agora s se aos parmetros passados por valor -
PARMETROS QUE VOC NO PODE MUDAR. Quando voc estiver por a brincando com
PARMETROS PASSADOS POR REFERNCIA, como -- MyProc(Var A, B, C : Word); --
cada parmetro agora usa QUATRO BYTES de pilha, dois para o segmento e dois
para o offset de onde a varivel est na memria.

Assim, se voc pegou uma varivel que est, digamos, na posio de memria
4AD8:000Eh, no interessa o valor dela, 4AD8:000Eh seria armazenado na pilha.

J que isso acontece, voc ia ver 0E 00 D8 4A na pilha, lembrando que o
nibble menos significativo  armazenado primeiro.

FATO ENGRAADO: Parmetros de Valor pe na verdade o valor na pilha, e
                Parmetros de Referncia armazenam o endereo. :)

 

Ok, agora que eu tenho voc realmente confuso, e com razo, isso piora!

Para referenciar estes parmetros no seu cdigo, voc tem que usar o ponteiro
de pilha, SP. O problema  que voc no pode brincar com SP diretamente, voc
tem que botar BP na pilha, e mover SP para ele. Isso agora coloca mais dois
bytes na pilha. Digamos que BP era igual a 0A45h. Antes de colocar BP, a
pilha era assim:

   4C EF 43 12 0F 00 14 00 0A 00

   ^^^^^^^^^^^ ^^^^^ ^^^^^ ^^^^^
      CS:IP    Color   Y     X

Depois, ela ficou assim:


 45 0A 4C EF 43 12 0F 00 14 00 0A 00

  ^^^^ ^^^^^^^^^^^ ^^^^^ ^^^^^ ^^^^^
   BP     CS:IP    Color   Y     X

Agora que ns passamos por isso tudo, podemos realmente acessar essas as
porcarias! O que voc faria depois de chamar -- PutPixel(10, 20, 15); --
para acessar o valor de Color  isto:

   PUSH  BP
   MOV   BP, SP

   MOV   AX, [BP+6]   ; Agora temos Color


Podemos acessar X e Y assim:

   MOV   BX, [BP+8]   ; Agora temos Y

   MOV   CX, [BP+10]  ; Agora temos X


E agora restauramos BP:

   POP   BP


Agora, retornamos de uma chamada FAR, e removemos os seis bytes de dados que
pusemos na pilha:

   RETF  6


E  s isso!


 

Agora vamos por a PutPixel, InitMCGS e Init80x25 em cdigo Assembler.
Voc obtm algo assim:

 

CODE SEGMENT WORD PUBLIC
     ASSUME  CS:CODE DS:DATA

     PUBLIC PutPixel        ; Declara as procedures pblicas
     PUBLIC InitMCGA
     PUBLIC Init80x25

.386                        ; Vamos usar alguns registradores de 386 

; 

;
; Procedure PutPixel(X, Y : Integer; Color : Byte);
;


PutPixel PROC FAR           ; Declara uma procedure FAR

   PUSH  BP
   MOV   BP, SP             ; Arruma a pilha
   
   MOV   BX, [BP+10]        ; BX = X
   MOV   DX, [BP+08]        ; DX = Y
   XCHG  DH, DL             ; Como Y sempre ter um valor menor quee 200,
   MOV   AL, [BP+06]        ; isto  320x200, no se esquea, dizer XCHG DH,DL
   MOV   DI, DX             ;  um modo genial de dizer SHL DX, 8
   SHR   DI, 2
   ADD   DI, DX
   ADD   DI, BX             ; Agora temos o offset, ento...
   MOV   FS:[DI], AL        ; ...plote em FS:DI

   POP   BP
   RETF  6

PutPixel ENDP

; 

;
; Procedure InitMCGA;
;

InitMCGA PROC FAR

   MOV   AX, 0A000H         ; Aponta AX para a VGA
   MOV   FS, AX             ; Porque no FS?
   MOV   AH, 00H
   MOV   AL, 13H
   INT   10H
   RETF

InitMCGA ENDP

; 

;
; Procedure Init80x25;
;

Init80x25 PROC FAR

   MOV   AH, 00H
   MOV   AL, 03H
   INT   10H
   RETF

Init80x25 ENDP

CODE    ENDS
        END


 

E  s. Desculpe-me se eu fiz a coisa toda um pouco confusa, mas essa  a
graa dos computadores! :)

Ah! A propsito, voc pode usar o cdigo acima em Pascal, assemblando-o com
TASM ou MASM. Depois, inclua no seu cdigo isso:

{$L SEJALDOQUEVOCCHAMOU.OBJ}
{$F+}
Procedure PutPixel(X, Y : Integer; Color : Byte);   External;
Procedure InitMCGA;                                 External;
Procedure Init80x25;                                External;
{$F-}

Begin
   InitMCGA;
   PutPixel(100, 100, 100);
   ReadLn;
   Init80x25;
End.


 

         Ŀ
                                                                   
                     FUNES E OTIMIZAES POSTERIORES             
                                                                   
         


Voc pode fazer suas rotinas Assembler retornarem valores que voc pode usar
em sua linguagem de alto-nvel, se voc quiser. A tabela abaixo contm
toda a informao que voc precisa saber.


                  ͻ
                   Tipo a Retornar  Registrador(es)a Usar 
                  Ķ
                    Byte              AL                  
                    Word              AX                  
                    LongInt           DX:AX               
                    Pointer           DX:AX               
                    Real              DX:BX:AX            
                  ͼ

Agora que voc j viu como escrever cdigo externo, voc provavelmente quer
saber como melhor-lo para obter a performance total que o cdigo externo
pode oferecer.

Alguns pontos para voc trabalhar se seguem:
                                            
    No se pode trabalhar com SP diretamente, mas voc pode usar ESP.

    Isso vai acabar com a lentido de empilhar/desempilhar BP.

    Lembre-se de mudar [xx+6] para [xx+4] para o ltimo (primeiro) parmetro,
     j que BP no est mais na pilha.

Gaste um tempo e veja o que voc pode fazer com isso.  possvel atravs de
melhorias, fazer um cdigo mais rpido que a rotina no MODE13H.ZIP verso 1
(disponvel na minha homepage).


Nota:  Eu planejo mais pra frente desenvolver a biblioteca MODE13H,
       adicionando fontes e outras coisas legais. Ela ser eventualmente
       codificada s em Assembler, e poder ser chamada do C ou Pascal.

       Cdigo puro em Assembler tambm tem um grande aumento de velocidade.
       Hoje eu testei a rotina PutPixel da biblioteca MODE13H e uma pura
       (praticamente idntica), e vi uma diferena espantosa. 

       Num 486SX-25 com 4Mb de RAm e uma placa VGA de 16 bits, levou 5
       centssimos de segundo para a rotina pura desenhar 65536 pixels
       no meio da tela, contra 31 centsimos de segundo da outra. Grande
       diferena, no?


 

         Ŀ
                                                                   
                                OTIMIZAO                         
                                                                   
         


Por mais rpido que o Assembler seja, voc sempre pode acelerar as coisas.
Eu vou falar como acelerar seu cdigo no 80486, e no 80386.

Eu no vou me preocupar muito com o Pentium por enquanto, j que os truques
de uso do Pentium so realmente truques, e demoraria um pouco para explicar.
Tambm, voc deveria evitar cdigo especfico para Pentium (embora isso
esteja mudando lentamente).


 

  A AGI (Address Generation Interlock):

Que merda  essa?, voc pergunta. Uma AGI ocorre quando uma registrador que
est correntemente sendo usado como base ou ndice foi o destino da ltima
instruo. AGI's so ruins, e chupam clocks.


EX.:   MOV   ECX, 3
       MOV   FS, ECX

Isso pode ser evitado executando-se uma outra instruo entre os dois MOV's,
pois AGI's podem ocorrer s entre instrues adjacentes (no 486). No Pentium,
uma AGI pode acontecer at entre 3 instrues!

 

  Use Instrues/Registradores de 32 bits:


Usar registradores de 32 bits tende a ser mais rpido que usar seus
equivalentes de 16 (particularmente EAX, j que muitas instrues ficam
um byte menor quando ele  usado. Usar DS ao invs de ES tambm  mais
rpido pelo mesmo motivo).

 

  Outras coisas para se tentar:


    Evite LOOP's. tente usar um DEC, ou INC seguido de um JZ ou instruo
     similar. Isso pode fazer uma diferena enorme.

    Quando for zerar registradores, use XOR ao invs de MOV xx, 0. acredite
     ou no,  mesmo mais rpido.

    Use o TEST quando for checar se um registrador  igual a zero. Em se
     fazer um AND dos operandos juntos no se gasta tempo com um registrador
     destino. TEST EAX,EAX  um bom mode de checar de EAX=0.

    USE SHIFTS! No use multiplicao para calcular mesmo as mais simples
     somas. A CPU pode mover uns poucos zeros para a esquerda ou para a
     direita muito mais rpido que ela pode fazer uma multiplicao/diviso.

    Faa uso da LEA. Uma instruo  tudo o que leva para realizar uma
     multiplicao inteira e armazenar o resultado num registrador. Esta 
     uma alternativa til para SHL/SHR (eu sei, eu sei... eu disse que a
     multiplicao era ruim. Mas uma LEA s vezes pode ser til, j que pode
     economizar vrias instrues.).

     EX.: LEA ECX, [EDX+EDX*4]   ; ECX = EDX x 5


    Evite fazer MOV's para registradores de segmento muito frequentemente.
     Se voc vai trabalhar com um valor que no muda, tal como A000h, ento
     carregue-o em FS, por exemplo, e use FS da em diante.

    Acredite ou no, instrues de string (LODSx, MOVSx, STOSx))so muito
     mais rpidas num 386 que num 486. Se estiver trabalhando num 486 ou mais,
     ento use outra instruo, mais simples.

    Quando for mover pedaos de 32 bits, REP STOSD  mais rpido que usar
     um loop para fazer a mesma coisa.

 

Bem, agora voc j viu como escrever cdigo externo, declarar procedures
em Assembler e otimizar suas rotinas. Na prxima semana eu finalmente vou
descrever tudo o que temos aprendido juntos, e ver se faz algum sentido.
Vou tambm incluir um exemplo em Assembler puro - um starfield melhor, com
controle de palette, para demonstrar INs e OUTs, controle de programa,
procedures e TEST's.


 

No prximo tutorial vamos ver:

    Uma reviso de tudo que aprendemos - finalmente (desculpem-me!);
    Declarar sub-procedures em Assembler;
    Um exemplo legal;  :)
    Algum outro tpico grandioso.

Se voc deseja ver um tpico discutido num tutorial no futuro, escreva-me, e
eu vou ver o que eu posso fazer.


 

No perca!!! Baixe o tutorial da prxima semana na minha homepage:

   http://www.faroc.com.au/~blackcat
   http://www.geocities.com/SiliconValley/Park/3174


Vejo vocs na prxima semana!

- Adam.
- Renato Nunes Bastos


